Held-checkout archiving holds the claim-ownership lock and starves ownership checks for minutes
Maintainers usually reply within 1 day
Assessment
This issue has not been assessed yet.
Description
Problem
retireHeldCheckout() in server/services/heldCheckoutRecovery.js runs archiveCheckout() — a full byte-preserving archive of the checkout (tracked, untracked, ignored files and Git administration) — inside withClaimOwnershipLock. That lock is the single process-wide queue behind every claim-ownership check/reserve/bind/release (ownershipTransaction in server/services/cosClaimOwnership.js), whose callers give up after CLAIM_OWNERSHIP_QUEUE_TIMEOUT_MS (15 s) with ownership-queue-timeout.
When the deterministic held-checkout recovery retires several checkouts back to back, the queue is starved for many minutes. Every ownership check during that window is refused.
Evidence (redacted, one incident on 2026-10-20)
- The last
🔒 Claim ownership … status=settledline came at about 18:14 UTC. After that, everyreserveloggedstatus=queue-timeout phase=ownershipQueue ownershipQueueMs≈15000, with nopending/startedoperation, until the first admission at 18:32 UTC: ~18 minutes. - In the same window:
🧹 held-checkout recovery: retired …claim-…-issue-12569(18:26),…issue-12573(18:28). - Effect: three concurrent branch-reconcile sub-agents each got
admitted:falsefor 10+ minutes on PRs that were green and CLEAN. One of them gave up and merged without a successful recheck (a protocol breach the gate is meant to prevent). The other was held until the queue cleared.
Expected vs actual
- Expected: an ownership check queues behind at most one short critical section (fresh owner read, compare, persist), as the 15 s budget assumes.
- Actual: it queues behind arbitrarily long archive I/O for N checkouts in sequence.
Chosen fix
Do the archive outside the claim-ownership lock and keep only the fences inside it:
- Under the lock:
matchReleaseplus owner/inventory/review checks. Then take a per-checkout retirement reservation (the existingwithCheckoutPublication/CHECKOUT_RETIREMENT_RESERVEDadmission inserver/lib/checkoutRetirementAdmission.js) so no claim can bind that checkout while it is being archived. Release the global lock. - Without the global lock:
archiveCheckout(). - Re-take the lock for the existing second
matchRelease→verifyRemoval(administration and content fingerprints) → removal. The fingerprint comparison already detects any change made during the archive, so moving the archive out does not weaken preservation.
Keep all current refusal reasons, the fingerprint-last ordering, and started-mutation settlement. Apply the same pattern to branchReconcile.js removal (withClaimOwnershipLock around line 1569) if it also archives under the lock.
Acceptance criteria
-
archiveCheckoutno longer runs whilewithClaimOwnershipLockis held. A deterministic test with a deferred archive shows a concurrentcheckClaimOwnershipsettling while the archive is pending. - A claim
bindfor the checkout being retired is refused (checkout-retirement-reserved) for the whole archive, and is never admitted halfway through. - A change to the checkout content or administration during the archive still yields
checkout-content-changed/checkout-administration-changed, and the checkout is kept. - Existing heldCheckoutRecovery and claim-ownership suites pass unchanged in intent.
model:heavy because this reorders lock and fence boundaries around destructive removal. effort:high because the preservation invariants must be re-proven at each boundary.
- Dominant language
- JavaScript
- Stars
- 38
- Forks
- 32
- Avg merge
- 35m
- Merged PRs (30d)
- 993
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from atomantic/PortOS
-
bug effort:medium model:light tests
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
area:cos-agents bug effort:low model:light plan planner:sonnet-5-5
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
area:federation bug effort:medium model:light plan planner:sonnet-5-5
Difficulty 3/5 1-2 days Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
musicVideoSceneHooks test can leak its temp root past afterAll and fail the suite's temp-leak guardOpenbug effort:medium model:light plan planner:sonnet-5-5 tests
Difficulty 4/5 3-5 days Newbie friendliness 55/100
Maintainers usually reply within 1 day
-
area:cos-agents bug effort:high model:medium plan planner:sonnet-5-5
Difficulty 4/5 3-5 days Newbie friendliness 57/100
Maintainers usually reply within 1 day
All issues in atomantic/PortOS
Similar issues
-
[Bug]: fileUploadMatrix.test.ts URL case does a real DNS lookup and fails offlinePossibly taken @r2jaff claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ComposioHQ/composio#5054 ·
Maintainers usually reply within 1 day
-
W3cOpen
Difficulty 1/5 Under an hour Newbie friendliness 88/100
web-platform-tests/wpt#64017 ·
Maintainers usually reply within 1 day
-
routine:ci-review
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
harlan-zw/unlighthouse#456 · 1 comment ·
Maintainers usually reply within 1 day
-
beginner enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
philaconvalley/website#217 ·
Maintainers usually reply within 1 day
-
[Bug] xiaohongshu search_input_not_available on accounts with new AI-search layout (gray rollout)Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 2 days