Team config.json non-atomic write corrupts state for concurrent readers

Resolved 💬 1 comment Opened Apr 27, 2026 by CH-JayShanmugam Closed May 30, 2026

Summary
The harness writes ~/.claude/teams/<team-name>/config.json on TeamCreate and on every Agent() member registration without atomic write semantics — concurrent external readers (custom hooks, monitoring tools) intermittently see partially-written / truncated content. Because every external jq-based reader fails-open on parse error, this silently disables downstream safety hooks during exactly the spawn window when those hooks are most needed.

Reproduction

  1. Start a Claude Code session with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1.
  2. Create a team via TeamCreate(team_name: "mx-test").
  3. From a separate shell, run a tight read loop against the file the harness is writing:

while true; do
jq '.members | length' ~/.claude/teams/mx-test/config.json 2>/dev/null \
|| echo "PARSE-FAIL @ $(date +%H:%M:%S.%N)"
sleep 0.05
done

  1. Spawn 2-3 agents via Agent() calls. Watch the loop.
  2. Observe PARSE-FAIL lines appear during the spawn window. Frequency low but non-zero. After the write completes, the file is well-formed when re-read — confirming transient mid-write corruption, not persistent.

Observed behavior

  • jq parse failures during the harness write window (estimated <100ms per write but enough to catch).
  • File is well-formed before and after; the corruption window is the write itself.
  • Every external CH-side hook in our harness ends up with a defensive pattern:

if ! command -v jq >/dev/null 2>&1; then exit 0; fi
result=$(jq -r '.members[]' "$cfg" 2>/dev/null) || exit 0

Not because jq is missing — because jq parse failures during normal Agent() activity were observed often enough to require defense.

  • Our own atomic-write helpers using tmp + mv -f (e.g. ~/.claude/hooks/resolve-team-name.sh:172) for parallel-written files in the same directory tree NEVER show this behavior, confirming the issue is the harness's write path, not a filesystem oddity.

Expected behavior
Writes to config.json (and any other harness-managed file under ~/.claude/teams/) should use atomic write:

  1. Open <target>.tmp.<pid> in the same directory.
  2. Write full content + fsync().
  3. rename(2) over the target.

POSIX rename within the same filesystem is atomic; readers see either the old content or the new content, never an intermediate state. This is the standard Unix pattern.

If atomic write is already in place upstream, please document where, and we'll re-investigate our hooks for another source of intermittent parse failures.

Why this matters
External tooling treats config.json as the canonical source of truth for team membership and member liveness. Non-atomic writes mean:

  1. No recovery from a writer crash mid-write — the file is left in a permanently corrupt state with no automatic rollback.
  2. External safety checks silently disable themselves during the spawn window — exactly when they're most needed (the spawn window is when phantom races happen).
  3. Slow writers effectively disable every gate hook — if the harness takes 200ms to write the file (e.g., on a slow disk), every gate hook reading during that window fails-open and allows whatever spawn the user requested.

This is a quiet correctness bug. Easy to mistake for "hooks sometimes don't run" or "gate intermittently fails," when the real cause is read-during-write.

Environment

OS: macOS 24.6.0 (Darwin Kernel) ARM64
claude: 2.1.119 (Claude Code)
bun: 1.3.5
Filesystem: APFS
Files: ~/.claude/teams/<team>/config.json
Flag: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗