Context
packages/loopover-miner/lib/attempt-cli.js:461-464 reads the cap (activeClaims.length >= minerGoalSpec.spec.maxConcurrentClaims), then at line 512 records the claim via a separate claimLedger.claimIssue(...) call with no shared transaction between the two. claimLedger.claimIssue (claim-ledger.js:229) does nothing but sweepExpiredClaims + recordClaim, and recordClaim is atomic only per-issue-uniqueness (INSERT ... ON CONFLICT(api_base_url, repo_full_name, issue_number)) — it performs no repo-wide count/cap check of its own. The soft-claim's own doc comment (lines 507-509) confirms concurrent sibling processes are expected: "a sibling miner process on this machine sees it via claimLedger.listActiveClaims while this attempt is in flight." Two concurrent loopover-miner attempt invocations against the same repo can therefore both read the same activeClaims.length before either commits its claim, both proceed past the cap check, and exceed the configured maxConcurrentClaims. test/unit/miner-attempt-cli.test.ts:1691-1742 only exercises the single-process already-at-cap case, never two racing calls.
Requirements
- Enforce
maxConcurrentClaims atomically at the point of claim — a single SQL statement/transaction in claim-ledger.js that counts active claims for the repo and conditionally inserts, rather than a separate count-then-claim split across attempt-cli.js.
- Ensure the cap violation is still observably reported/logged for the loser, not just silently dropped.
Deliverables
Test Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch, plus a regression test that reproduces the exact failure mode described above and asserts it's fixed.
Expected Outcome
Two sibling miner processes can never both exceed the configured per-repo concurrent-claim cap, even when racing.
Links & Resources
packages/loopover-miner/lib/attempt-cli.js:461-464,508-512, packages/loopover-miner/lib/claim-ledger.js:229.
Context
packages/loopover-miner/lib/attempt-cli.js:461-464reads the cap (activeClaims.length >= minerGoalSpec.spec.maxConcurrentClaims), then at line 512 records the claim via a separateclaimLedger.claimIssue(...)call with no shared transaction between the two.claimLedger.claimIssue(claim-ledger.js:229) does nothing butsweepExpiredClaims+recordClaim, andrecordClaimis atomic only per-issue-uniqueness (INSERT ... ON CONFLICT(api_base_url, repo_full_name, issue_number)) — it performs no repo-wide count/cap check of its own. The soft-claim's own doc comment (lines 507-509) confirms concurrent sibling processes are expected: "a sibling miner process on this machine sees it via claimLedger.listActiveClaims while this attempt is in flight." Two concurrentloopover-miner attemptinvocations against the same repo can therefore both read the sameactiveClaims.lengthbefore either commits its claim, both proceed past the cap check, and exceed the configuredmaxConcurrentClaims.test/unit/miner-attempt-cli.test.ts:1691-1742only exercises the single-process already-at-cap case, never two racing calls.Requirements
maxConcurrentClaimsatomically at the point of claim — a single SQL statement/transaction inclaim-ledger.jsthat counts active claims for the repo and conditionally inserts, rather than a separate count-then-claim split acrossattempt-cli.js.Deliverables
claim-ledger.jsmaxConcurrentClaims=1; assert only one succeedsTest Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch, plus a regression test that reproduces the exact failure mode described above and asserts it's fixed.
Expected Outcome
Two sibling miner processes can never both exceed the configured per-repo concurrent-claim cap, even when racing.
Links & Resources
packages/loopover-miner/lib/attempt-cli.js:461-464,508-512,packages/loopover-miner/lib/claim-ledger.js:229.