feat(approvals): atomic claim to fix concurrent-exec race (v0.3.1)#7
Merged
feat(approvals): atomic claim to fix concurrent-exec race (v0.3.1)#7
Conversation
Two concurrent agentcli exec calls on the same gated task could both observe the same pending grant and both append consume events, allowing both to execute from one approval. Fixed by introducing claimApproval, a single atomic primitive that performs find-then-consume under an fs-lockfile. Lock implementation (no new deps): - openSync(lockPath, 'wx') for atomic creation - Atomics.wait on SharedArrayBuffer for sync backoff during contention - Stale locks (older than 30s) are treated as abandoned and broken - approval_lock_timeout (default 5s) surfaces genuine contention enforceApprovalGate in src/exec.js now calls claimApproval instead of findValidApproval + consumeApproval separately. Signature verification still runs after the atomic claim; a bad signature refuses execution with approval_signature_invalid (the consumed grant is audited). Tests added (4): - N=8 worker threads racing on one grant → exactly 1 winner - 2 pending grants + 2 concurrent claims → both succeed, distinct grants - Stale lock (hour-old mtime) is broken and claim proceeds - Fresh lock held past timeout raises approval_lock_timeout claimApproval added to the barrel export. Previously-exported consumeApproval remains for callers that consume without claiming; approvals.js module comment updated to describe the new atomicity. Release cut to 0.3.1 (bug fix, no schema or CLI surface changes).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two concurrent
agentcli execcalls on the same gated task could both observe the same pending approval and both consume it, allowing both to execute from one grant. This PR addsclaimApproval: an atomic find-then-consume primitive wrapped in an fs-lockfile.enforceApprovalGateinsrc/exec.jsnow usesclaimApprovalin place of the separatefindValidApproval+consumeApprovalpair. Concurrent claims serialize to exactly one winner per grant.Cuts v0.3.1 (pure bug fix, no CLI or manifest schema changes).
Design
openSync(lockPath, 'wx')for atomic lock creation (works on macOS/Linux/Windows)Atomics.waiton a SharedArrayBuffer for sync backoff during contention — no new npm deps, no busy-waiting, no child processesapproval_lock_timeout(default 5s contention window) surfaces genuine deadlocksTest plan
npm run lintcleannpm testgreen — 663/663 (was 659 + 4 new concurrency tests)approval_lock_timeoutwith correct error codelint-testpassesFiles
src/approvals.js—withApprovalsLockhelper,claimApprovalexported, updated module-level commentsrc/exec.js—enforceApprovalGaterewired toclaimApproval, import list adjustedsrc/index.js— barrel export ofclaimApprovalsrc/describe.js—approvecommand summary mentions serializationtest/approvals.test.js— 4 new concurrency testspackage.json— 0.3.0 → 0.3.1CHANGELOG.md— new 0.3.1 section