Skip to content

Parallel hooks in one turn share a scratch index — index.lock race yields spurious 'could not snapshot' skips #14

Description

@AventusM

Symptom

When several hooks of the same turn run concurrently (parallel tool calls), all but one write a skip event:

{"kind":"skip","phase":"turn","reason":"turn diff incomplete: git could not snapshot the working tree in time"}

Root cause (measured)

Every hook in a turn snapshots through ONE shared scratch index: snapshotTree(root, path.join(turnDir(session, turn), "index")) (packages/cli/src/hooks/stop.ts, turnStart.ts). Concurrent git add -A runs under the same GIT_INDEX_FILE collide on index.lock; losers die in ~30ms with fatal: Unable to create '.../index.lock': File exists (nonzero exit, not a timeout). snapshotTree (packages/cli/src/lib/git.ts) returns undefined for that, and Stop maps every undefined to the same timeout message, so fast lock failures are indistinguishable from slow git.

Toggle matrix with the real snapshotTree (Apple Git 2.50.1):

  • sequential, shared scratch, 3x: 3/3 ok
  • 12 parallel, unique scratch each: 12/12 ok
  • 6 parallel, shared scratch: 1/6 ok (repeated twice)

Isolated snapshots take 50-260ms vs the 5s/8s budgets, so this is purely the lock race, in any checkout (main or fresh worktree).

Suggested fix

Use a per-invocation scratch path (e.g. index.<pid>) or retry git add past a transient lock, and give lock contention its own skip message so it stops masquerading as slow git.

Repro: copy any repo index to a scratch path, then run 6x GIT_INDEX_FILE=<scratch> git add -A -- . in parallel — 5 fail with the lock error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions