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
backend/internal/adapters/agent/goose/hooks.go (~line 242-261)
backend/internal/adapters/agent/kiro/hooks.go (~line 240-258)
backend/internal/adapters/agent/cline/hooks.go (~line 183-202)
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
- Start a session with goose/kiro/cline/autohand adapter
- The adapter writes hook configuration via its local
atomicWriteFile
- 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)
- 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.
Bug
Four agent adapters have local copies of
atomicWriteFilethat skip thetmp.Sync()call beforetmp.Close()andos.Rename(). The canonical implementation inhookutil/hookutil.go:74correctly callsSync(). 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(currentmain)Confidence: High — compared all
atomicWriteFilecopies against the canonicalhookutilimplementation.Affected Files
backend/internal/adapters/agent/goose/hooks.go(~line 242-261)backend/internal/adapters/agent/kiro/hooks.go(~line 240-258)backend/internal/adapters/agent/cline/hooks.go(~line 183-202)backend/internal/adapters/agent/autohand/hooks.go(~line 305-324)All four go from
tmp.Chmod()→tmp.Close()→os.Rename()withouttmp.Sync()in between.The canonical implementation at
backend/internal/adapters/agent/hookutil/hookutil.go:74:Reproduction
atomicWriteFileos.Renameand the kernel flushing the file data to disk, the hook config file can be:Most likely to occur on
data=writebackext4 filesystems under heavy I/O, or on systems with aggressive writeback caching.Impact
hookutil.AtomicWriteFile(which hasSync()) or don't have file-based hooksSuggested Fix
Add
tmp.Sync()to each of the 4 local copies, matching the canonical pattern:Better yet, refactor all 4 to use the shared
hookutil.AtomicWriteFileinstead of maintaining local copies.