taskCommit (project_task.go) guards with taskHasRunningJob (line 375), recomputes the fingerprint (395), compares to expected_diff_sha256 (399), then git add -A (402) and commit (405) — with no lock held across these steps. taskHasRunningJob is a point-in-time check-then-act, not a lease: an agent.message_send can write its running record and spawn the agent subprocess after the guard read but before git add -A, so git add -A stages unpreviewed, unfingerprinted edits — exactly what the fingerprint gate and the guard were meant to prevent (see the developers' own comment at project_task.go:372-374).
Fix belongs with the R0 per-worktree write lock (0.3.0): hold a task/worktree lease across the fingerprint check → git add → commit window, and have agent jobs bound to a task take the same lease. Distinct from #24 (file.edit_apply same-file race) and #54 (fingerprint stderr parsing).
taskCommit(project_task.go) guards withtaskHasRunningJob(line 375), recomputes the fingerprint (395), compares toexpected_diff_sha256(399), thengit add -A(402) and commit (405) — with no lock held across these steps.taskHasRunningJobis a point-in-time check-then-act, not a lease: anagent.message_sendcan write its running record and spawn the agent subprocess after the guard read but beforegit add -A, sogit add -Astages unpreviewed, unfingerprinted edits — exactly what the fingerprint gate and the guard were meant to prevent (see the developers' own comment at project_task.go:372-374).Fix belongs with the R0 per-worktree write lock (0.3.0): hold a task/worktree lease across the fingerprint check → git add → commit window, and have agent jobs bound to a task take the same lease. Distinct from #24 (file.edit_apply same-file race) and #54 (fingerprint stderr parsing).