Skip to content

bug(adapters): 4 agent adapters missing tmp.Sync() in atomicWriteFile — hook config can be silently corrupted on crash #270

@fireddd

Description

@fireddd

Bug

Four agent adapters have local copies of atomicWriteFile that skip the tmp.Sync() call before tmp.Close() and os.Rename(). The canonical implementation in hookutil/hookutil.go:74 correctly calls Sync(). Without it, a system crash (OOM kill, power loss, kernel panic) during the rename window can leave an empty or truncated hook config file, silently disabling AO hooks for that agent.

Analyzed against: 96d1649 (current main)
Confidence: High — compared all atomicWriteFile copies against the canonical hookutil implementation.

Affected Files

  1. backend/internal/adapters/agent/goose/hooks.go (~line 242-261)
  2. backend/internal/adapters/agent/kiro/hooks.go (~line 240-258)
  3. backend/internal/adapters/agent/cline/hooks.go (~line 183-202)
  4. backend/internal/adapters/agent/autohand/hooks.go (~line 305-324)

All four go from tmp.Chmod()tmp.Close()os.Rename() without tmp.Sync() in between.

The canonical implementation at backend/internal/adapters/agent/hookutil/hookutil.go:74:

if err := tmp.Sync(); err != nil { ... }  // ← present in canonical
if err := tmp.Close(); err != nil { ... }
return os.Rename(...)

Reproduction

  1. Start a session with goose/kiro/cline/autohand adapter
  2. The adapter writes hook configuration via its local atomicWriteFile
  3. If the system crashes between os.Rename and the kernel flushing the file data to disk, the hook config file can be:
    • Empty (file metadata written, data not yet flushed)
    • Truncated (partial data flushed)
  4. On next session start, the agent loads the corrupted config and silently has no AO hooks

Most likely to occur on data=writeback ext4 filesystems under heavy I/O, or on systems with aggressive writeback caching.

Impact

  • Silent hook loss: AO activity signals stop flowing, and the lifecycle manager can't observe agent state
  • Affects 4 of the 20+ agent adapters
  • The other adapters either use the canonical hookutil.AtomicWriteFile (which has Sync()) or don't have file-based hooks

Suggested Fix

Add tmp.Sync() to each of the 4 local copies, matching the canonical pattern:

if err := tmp.Sync(); err != nil {
    return fmt.Errorf("sync temp file: %w", err)
}

Better yet, refactor all 4 to use the shared hookutil.AtomicWriteFile instead of maintaining local copies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions