fix(selfhost): ownership tokens for transient locks with claim/releaseIfValue pairing - #3161
fix(selfhost): ownership tokens for transient locks with claim/releaseIfValue pairing#3161RealDiligent wants to merge 4 commits into
Conversation
Per-PR actuation and AI-review mutexes claimed Redis keys with a constant value and released via blind del(). A holder running past the TTL could delete a successor's live lock in finally, reopening merge/close races the mutex exists to prevent (JSONbored#2129/JSONbored#2135). Store a per-holder UUID at claim time and release with compare-and-delete (releaseIfValue) on the Redis cache adapter. Skip release when fail-open (no cache) or when the adapter lacks compare-and-delete (TTL backstop). Co-authored-by: Cursor <cursoragent@cursor.com>
Add regression tests for caches without releaseIfValue and for releaseIfValue failures so stale-holder protection branches are fully exercised. Co-authored-by: Cursor <cursoragent@cursor.com>
…behavior Co-authored-by: Cursor <cursoragent@cursor.com>
Ownership-token release fixed stale-holder blind del() (JSONbored#2129), but skipping release when releaseIfValue was absent pinned locks for 600s/1800s after normal work on misconfigured adapters (JSONbored#3153). - Boot: assertSelfhostTransientCacheOwnershipRelease() in server.ts - Runtime: fail open without calling claim() when releaseIfValue is missing - Tests: stale-holder regressions for both lock namespaces, boot guard, JSONbored#3153 path Co-authored-by: Cursor <cursoragent@cursor.com>
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report❌ Patch coverage is
❌ Your patch status has failed because the patch coverage (93.10%) is below the target coverage (99.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #3161 +/- ##
==========================================
- Coverage 95.15% 95.15% -0.01%
==========================================
Files 268 268
Lines 29391 29406 +15
Branches 10712 10717 +5
==========================================
+ Hits 27967 27980 +13
- Misses 780 781 +1
- Partials 644 645 +1
🚀 New features to boost your workflow:
|
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 Gittensory review result - fixes requiredReview updated: 2026-07-04 18:19:07 UTC
🛑 Suggested Action - Fix Blockers
Review summary Nits — 6 non-blocking
CI checks failing
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
|
Gittensory is closing this pull request on the maintainer's behalf (CI is failing (codecov/patch); conflicts with the base branch — resolve and open a fresh PR). This is an automated maintenance action — to pursue this change, please open a new pull request with the issues resolved. Closed PRs may be analyzed later to improve review accuracy, but they are not automatically reopened or re-reviewed. |
Summary
Fixes the transient PR actuation / AI-review lock stale-holder race (#2129/#2135) using ownership tokens and compare-and-delete release, and addresses the critical defect that caused #3153 to be closed: adapters with
claim()but noreleaseIfValue()would pin locks for 600s/1800s after normal successful work.Supersedes closed #3153 and #2991.
Problem
Two related failure modes in the shared transient-cache mutex:
Stale-holder blind
del(): Lock values were constant"1"and release used unconditionaldel(key). A holder that ran past TTL could delete a successor's live lock infinally, reopening merge/close actuation races.Misconfigured adapter throughput collapse (fix(selfhost): use ownership tokens for transient PR actuation locks #3153): The first fix skipped release when
releaseIfValuewas absent (TTL backstop). Any adapter implementingclaim()without ownership-aware release would hold actuation locks for 10 minutes and AI-review locks for 30 minutes after every successful pass — blocking follow-up work on the same PR.Root cause
Lock release was not ownership-aware, and the initial ownership-token fix traded one bug for another on partially implemented cache adapters.
Implementation
releaseIfValue(key, value)on the Redis transient cache (Lua compare-and-delete).claimTransientLock()generates UUID owner tokens; release uses compare-and-delete only.assertSelfhostTransientCacheOwnershipRelease()inserver.tsrejectsclaim()withoutreleaseIfValueat startup.claimTransientLock()fails open without callingclaim()whenreleaseIfValueis missing — no exclusivity, but also no unreleasable lock.Testing performed
npm run typecheckownerToken: nullassertSelfhostTransientCacheOwnershipReleasethrows for claim-only adapters; passes forcreateRedisCachereleaseIfValueerror path (best-effort, TTL backstop)test/unit/selfhost-redis-cache.test.tsCompatibility
claimandreleaseIfValue(createRedisCache).claim()entirely (fail-open, no exclusivity).TransientLockClaiminstead ofboolean; all in-repo call sites updated.Why this approach
Compare-and-delete is the minimal correct fix for Redis-style transient locks. Pairing
claim+releaseIfValueat boot prevents silent misconfiguration; runtime fail-open without acquiring unreleasable locks preserves the defense-in-depth philosophy when tests inject partial mocks.Scope
Validation
npm run typecheckSafety
Notes
#3153 was auto-closed by Gittensory with: "releaseTransientLockIfOwner skip release whenever cache has claim but no releaseIfValue … keeps PR actuation locks for 600 seconds". This revision implements the suggested boot rejection plus runtime fail-open-without-claim behavior.