Repository navigation
v1.21.3 — one key, one name: a dotted lease is released under the name it was taken (#25) - #26
Merged
Merged
Conversation
…e it was taken (#25) Under leaseBackend "git" a key with a dot was reported and released under a name nothing was taken under. Root cause (1.21.2): the key was stored under two different lossy slugs — `_ref` kept dots (re.sub(r"[^A-Za-z0-9._-]+", "-", key).strip("-"), agent_sync.py:1586), `_local_lock` did not (re.sub(r"[^A-Za-z0-9_-]", "-", key), :1813), and `held()` returned the lock stem (:2588). `release --held` then released `T-1-2`, `_git_release` found no such ref and returned silently (:1763), and `release` printed "released" with the ref still on the remote. Path keys were stranded the same way, `a/b` and `a-b` shared one ref, and a leading-dot key could not be pushed (reported as "held by another run"). - lease_name/lease_key: one injective name (percent-escape outside [A-Za-z0-9._-], dots git forbids) for ref and note; payloads carry the key - acquire refuses keys that cannot travel unchanged (empty, whitespace, control, backtick, |, >200 bytes stored) and two spellings on one note file - release asks the remote first, deletes with --force-with-lease, re-reads it strictly, and only then clears claim and note; otherwise NOT released, exit 1 (also release --held and merge, which printed ✓ unconditionally) - 1.21.2 state stays exclusive and releasable: slugged refs read as the key's lease, legacy notes re-noted from the remote (CLI only, never the guard), the printed slug resolves to this run's 1.21.2 ref, local legacy locks block - reserve refuses register names the id ref cannot carry verbatim (A B and A-B shared one counter); release-id touches no ref; res-- keys unchanged Tests: eight new checks against a real bare origin + ls-remote; run against the 1.21.2 script seven fail with 37 problems. Eight self-test plants (slugged ref, slugged stem, held() stem, unverified delete, no migration, unread legacy ref, accepted bad key, slugged register) all caught; two existing plants re-anchored. Gate: npm test EXIT=0 — validate PASS v1.21.3 (also Python 3.9.6), SELF-TEST PASS (74 fixtures), claim cell 27, session hooks 13, installer 11; audit regressions, evals, social preview pass; pinned house audit 0 GAP / 18 PASS (body 4749/4750); claude plugin validate --strict passed for . and plugins/agent-sync. Open: AS-14 — a 1.21.2 and a 1.21.3 run racing one PATH key with no ref yet push two refs; update every machine. Closes #25 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Fixes #25. Under
leaseBackend: "git", a lease key containing a dot (T-1.2) was shown and released asT-1-2.release --heldprinted "released" and the ref stayed on the remote.Root cause (1.21.2). The key was stored under two different lossy slugs:
_refkept dots:re.sub(r"[^A-Za-z0-9._-]+", "-", key).strip("-")(agent_sync.py:1586)._local_lockturned them into dashes:re.sub(r"[^A-Za-z0-9_-]", "-", key)(:1813).held()returned the lock file's stem (:2588).release --heldthen releasedT-1-2._git_releasefound no ref with that name and returned without saying anything (:1763), andreleaseprintedreleased. The same slugs caused three more problems:docs/x.md) got stuck the same way.a/banda-bshared one ref.Fix
lease_name/lease_keygive the ref and the note one injective name: every byte outside[A-Za-z0-9._-]is percent-escaped, and so is any dot where git forbids one. Both payloads also carry the key verbatim, and that is whatheld/whoami/residuereport.acquirerefuses a key that cannot travel through the log, the board andwhoamiunchanged. It also refuses two spellings that resolve to one note file (T-1andt-1on a case-insensitive disk).releaseasks the remote first, deletes with--force-with-lease, then reads the remote again with a strict check. Only after that does it clear the board claim and the local note. If the remote refused the delete or could not be reached, it printsNOT released, exits 1, and keeps the claim and the note.release --heldandmergebehave the same way;mergeused to print✓ releasedno matter what.release T-1-2) resolves to that ref, but only for refs 1.21.2 wrote.reserve: it slugged register names, soA BandA-Bshared one counter. It now refuses a name the ref cannot carry verbatim. Every existing valid name keeps its ref.release-idonly writes to the log and never touches a ref, so it was not affected.res--resource keys round-trip unchanged.Tests (
test/validate.py, all against a real bare origin and confirmed withgit ls-remote): 8 new checks. Run against the 1.21.2 script, 7 of them fail with 37 problems. The legacy-exclusion guard passes on 1.21.2 by design. 8 self-test plants put the defect back in different ways (slugged ref, slugged stem,held()reporting the stem, unverified delete, no migration, legacy ref not read, bad key accepted, slugged register), and all 8 are caught. Two existing plants were re-anchored.Gate (local):
npm testEXIT=0:PASS: agent-sync v1.21.3 — all checks green(also on Python 3.9.6),SELF-TEST PASS(74 fixtures), claim cell 27, session hooks 13, installer 11. Audit regressions, evals and the social preview pass. The pinned house audit gives0 GAP, 18 PASS(body 4749/4750 tokens).claude plugin validate --strictpasses for.andplugins/agent-sync.Still open (backlog AS-14): if a 1.21.2 run and a 1.21.3 run race for the same path key before either ref exists, each pushes its own ref and both win. The fix is to update every machine. Task ids made only of safe characters are not affected.
🤖 Generated with Claude Code