Skip to content

fix(lock): retry unlinked sidecar snapshots - #185

Merged
clawsweeper[bot] merged 2 commits into
mainfrom
fix/sidecar-unlinked-snapshot
Aug 31, 2026
Merged

fix(lock): retry unlinked sidecar snapshots#185
clawsweeper[bot] merged 2 commits into
mainfrom
fix/sidecar-unlinked-snapshot

Conversation

@steipete

@steipete steipete commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • treat a Root-backed sidecar ownership record unlinked after open as absent so acquisition retries instead of surfacing path-mismatch
  • require a second absence proof through the same Root capability before normalizing the error
  • keep replacements, symlinks, hardlinks, root drift, and operational probe failures fail-closed
  • add deterministic helper and two-manager contention regressions for unlink before and after identity admission

Root cause

A waiter could open and identity-check an existing sidecar lock while its owner was releasing it. If the owner unlinked the pathname before Root.open() completed opened-path resolution, the still-valid descriptor had no resolvable name and Root.open() correctly threw FsSafeError("path-mismatch"). readSidecarLockSnapshot() propagated that generic Root policy error, so normal ownership turnover could abort acquisition instead of behaving like an absent lock and retrying create-only publication.

The ownership distinction belongs in the sidecar layer, not in Root.open(): generic Root callers must continue to reject unlinked or ambiguously named descriptors.

Fix

For Root-backed snapshots, readSidecarLockSnapshot() now catches only FsSafeError("path-mismatch") from the initial open and calls lockRoot.stat(relative) through the same capability. It returns null only when that second operation proves FsSafeError("not-found") under the still-valid root. Any existing replacement, changed root, symlink/non-file/hardlink policy result, raw ENOENT, or operational failure preserves the original mismatch.

Returning null after an observed absence is safe even if the name is recreated immediately afterward: the acquisition loop retries exclusive create, which either creates the new ownership record or rejects the replacement as contention.

Compiled runtime proof

A standalone proof now runs the compiled public file-lock and Root APIs against real filesystem operations. It pauses the waiter after pathname identity admission, releases the owner, verifies same-capability absence recovery and create-only reacquisition, then separately installs a replacement after open and verifies path-mismatch plus exact preservation:

{"proof":"sidecar-unlinked-snapshot","platform":"darwin","handoff":{"hookCalls":1,"waiterOwner":"waiter","payload":{"owner":"waiter","scenario":"handoff"},"verified":true,"openedHandleClosed":true,"finalMissing":true},"replacement":{"hookCalls":1,"error":{"name":"FsSafeError","code":"path-mismatch"},"openedHandleClosed":true,"replacementPreserved":true,"originalPreserved":true}}

The same proof runs on exact-head Node 24 Linux and macOS CI.

Verification

  • deterministic Root-backed helper regressions cover unlink before and after identity admission, handle closure, regular-file replacement preservation, root replacement, and every non-absence probe failure
  • deterministic POSIX two-manager contention proof: owner releases while waiter holds the opened descriptor; waiter resumes, retries, acquires, verifies ownership, and releases cleanly
  • focused sidecar suites: 3 files, 49 tests passed
  • security suite: 5 files, 78 tests passed
  • complete CI=1 pnpm check: 6,086 tests passed, 76 skipped; build, docs, filesystem-boundary lint, file-size budget, package, and API checks passed
  • Codex autoreview at P1: clean; no accepted/actionable findings
  • git diff --check: passed

Exact-head hosted Linux, macOS, Windows, coverage, package, analysis, and ClawSweeper review remain the landing gates. No release or package publication is part of this PR.

@steipete
steipete requested a review from a team as a code owner August 31, 2026 11:43
@clawsweeper

clawsweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@steipete

Copy link
Copy Markdown
Contributor Author

@clawsweeper automerge

Special instructions:
Review exact head 6a532994a25033cfebbb040cb87f07c22658a3e1. Verify the owner boundary stays in readSidecarLockSnapshot, not Root.open: only a Root-backed open that fails with path-mismatch and is followed by same-capability Root.stat(relative) returning not-found may become an absent snapshot/retry.

Require deterministic proof for unlink before and after identity admission, handle closure, a real two-manager owner-release/waiter-acquire handoff, replacement preservation, changed-root rejection, symlink/non-file/hardlink and operational probe failures, and create-only retry safety. Keep raw-path snapshots and generic Root behavior unchanged. Require fresh exact-head Node 22/24 Linux/macOS/Windows checks, three-OS coverage, native/package/audit/analysis/security gates, and no actionable review finding before merge.

@clawsweeper

clawsweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper merged this PR after the passing review.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=5581c300829ba572d61fefd725989509a44dd1c2)
Merge status: merged by ClawSweeper automerge
Merged at: 2026-08-31T12:11:08Z
Merge commit: a80d8f64d939

What merged:

  • The branch lets Root-backed sidecar-lock acquisition retry after a concurrently unlinked ownership record only when the same Root capability proves the path absent, with regressions and CI proof for handoff and replacement cases.

Automerge notes:

  • PR branch already contained follow-up commit before automerge: test(lock): prove unlinked sidecar handoff

The automerge loop is complete.

Automerge progress:

  • 2026-08-31 11:44:14 UTC active review reused 6a532994a250 (waiting)
  • 2026-08-31 11:47:41 UTC review queued 6a532994a250 (queued)
  • 2026-08-31 12:05:04 UTC review queued 5581c300829b (queued)
  • 2026-08-31 12:08:35 UTC review passed 5581c300829b (structured ClawSweeper verdict: pass (sha=5581c300829ba572d61fefd725989509a44dd...)
  • 2026-08-31 12:11:10 UTC merged 5581c300829b (merged by ClawSweeper automerge)

@clawsweeper clawsweeper Bot added clawsweeper:automerge Maintainer opted this ClawSweeper PR into bounded ClawSweeper-reviewed automerge merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 31, 2026
@clawsweeper

clawsweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Codex review: passed. Reviewed August 31, 2026, 8:07 AM ET / 12:07 UTC.

ClawSweeper review

What this changes

The branch lets Root-backed sidecar-lock acquisition retry after a concurrently unlinked ownership record only when the same Root capability proves the path absent, with regressions and CI proof for handoff and replacement cases.

Regression provenance

Possible regression — probable (reviewed change; failure trace). No predecessor PR is attributed.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

Keep open: current main still propagates the Root path-mismatch during this handoff, while this focused PR confines the retry exception to capability-proven absence and supplies compiled runtime evidence. It is suitable for its existing automerge path once the remaining exact-head hosted gates finish.

Priority: P2
Reviewed head: 5581c300829ba572d61fefd725989509a44dd1c2

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) A focused boundary-preserving fix with strong deterministic coverage and inspectable compiled-runtime handoff evidence.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.
Evidence reviewed 6 items Current-main gap: The main-parent implementation directly opens the Root-relative sidecar path and has no capability-backed absence retry, so the central behavior is not already implemented.
Narrow production boundary: The introduced handler only converts path-mismatch to an absent snapshot after lockRoot.stat(relative) returns fs-safe not-found; every other open or probe outcome rethrows the original mismatch.
Root capability remains authoritative: Root.stat() resolves through the pinned Root context and checks current root identity after the lookup, so the new probe does not bypass the guarded filesystem surface.
Findings None None.
Security None None.

How this fits together

The file-lock layer publishes a sidecar ownership record next to a protected target file. When exclusive creation collides, it reads that record through a Root capability, which enforces confinement and filesystem identity before acquisition either retries or fails closed.

flowchart TD
  A[Caller requests file lock] --> B[Exclusive sidecar create]
  B -->|record exists| C[Root-backed snapshot read]
  C --> D[Path and root identity checks]
  D -->|absence proven| E[Retry exclusive create]
  D -->|replacement or unsafe state| F[Fail closed]
  E --> G[Verified held lock]
Loading

Before merge

  • Resolve merge risk (P1) - This changes interpretation of a Root identity failure on a lock-contention path; preserving the exact path-mismatch plus same-capability not-found predicate is necessary to avoid weakening fail-closed behavior.
  • Complete next step (P2) - No repair is needed; the existing automerge path can wait for the remaining exact-head hosted checks.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Runtime-to-proof delta production +19/-1, tests/proof +401/-2, CI +6, changelog +1 The security-sensitive runtime exception is small relative to the deterministic regression and compiled-runtime coverage added around it.

Merge-risk options

Maintainer options:

  1. Merge with the guarded exception (recommended)
    Accept the narrowly bounded retry after the remaining exact-head checks confirm the capability-backed absence and fail-closed replacement behavior.

Technical review

Best possible solution:

Land the narrow sidecar-layer exception after the remaining exact-head hosted gates complete, retaining generic Root.open() failure semantics and the replacement/root-drift regressions.

Do we have a high-confidence way to reproduce the issue?

Yes. The introduced deterministic POSIX tests and compiled public-API trace define a high-confidence contention path, although this read-only review did not execute the pre-fix failure on current main.

Is this the best way to solve the issue?

Yes. The exception is placed in the sidecar ownership layer, not generic Root opening, and reuses the same guarded capability to distinguish confirmed absence from replacements or unsafe states.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against f4c5b321ea9f.

Labels

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (live_output): The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: This is a bounded correctness fix for lock acquisition under a narrow concurrent filesystem handoff.
  • merge-risk: 🚨 security-boundary: The PR changes when a Root path-identity failure can become retryable, so its strict capability-backed predicate is merge-critical.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (live_output): The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.
  • proof: sufficient: Contributor real behavior proof is sufficient. The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention; the supplied exact-head compiled public-API trace records owner release, Root path-mismatch, capability not-found, create-only reacquisition, handle closure, and separate replacement preservation.

Evidence

What I checked:

  • Current-main gap: The main-parent implementation directly opens the Root-relative sidecar path and has no capability-backed absence retry, so the central behavior is not already implemented. (src/sidecar-lock-reclaim.ts:102, f4c5b321ea9f)
  • Narrow production boundary: The introduced handler only converts path-mismatch to an absent snapshot after lockRoot.stat(relative) returns fs-safe not-found; every other open or probe outcome rethrows the original mismatch. (src/sidecar-lock-reclaim.ts:103, 5581c300829b)
  • Root capability remains authoritative: Root.stat() resolves through the pinned Root context and checks current root identity after the lookup, so the new probe does not bypass the guarded filesystem surface. (src/root-impl.ts:1427, f4c5b321ea9f)
  • Focused adversarial coverage: New POSIX tests cover unlink before and after identity admission, replacement preservation, root replacement, and rejected non-absence probe outcomes. (test/sidecar-lock-helpers-failure.test.ts:83, 5581c300829b)
  • Real behavior proof: The supplied exact-head compiled-runtime trace exercises two managers through public file-lock and Root APIs, observing successful create-only handoff and preserved replacement rejection; the script is wired into Node 24 Linux and macOS CI. (scripts/sidecar-unlinked-snapshot-proof.mjs:81, 5581c300829b)
  • Relevant feature history: Local feature history identifies steipete as a recent merged contributor to lock and Root identity hardening, including the prior sidecar release-failure work. (src/sidecar-lock-reclaim.ts:90, c558f9295666)

Likely related people:

  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (2 earlier review cycles)
  • reviewed 2026-08-31T11:45:50.358Z sha 6a53299 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-31T11:50:14.934Z sha 6a53299 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added the clawsweeper:human-review ClawSweeper automerge is paused for maintainer review label Aug 31, 2026
@clawsweeper

clawsweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper is pausing this repair loop for human review.

Source: clawsweeper[bot]
Reason: - [ ] Add real behavior proof - Needs real behavior proof before merge: The changed production owner is readSidecarLockSnapshot, reached after exclusive-create contention in sidecar acquisition; the supplied materia... (sha=6a532994a25033cfebbb040cb87f07c22658a3e1)

Why human review is needed:
ClawSweeper found a blocker that should be resolved or accepted by a maintainer before the repair or automerge loop continues.

What the maintainer can do as a next step:
If the maintainer accepts the current risk and wants ClawSweeper to continue merge gates, comment @clawsweeper approve. If more work is needed, resolve the blocker first, then comment @clawsweeper automerge to re-review and continue. If automation should stay paused, leave clawsweeper:human-review in place or comment @clawsweeper stop.

I added clawsweeper:human-review and left the final call with a maintainer.

@clawsweeper clawsweeper Bot removed the status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. label Aug 31, 2026
@steipete

Copy link
Copy Markdown
Contributor Author

@clawsweeper automerge

Special instructions:
Review exact head 5581c300829ba572d61fefd725989509a44dd1c2. The PR body now includes the requested inspectable runtime JSON from the compiled public file-lock and Root APIs.

Require exact-head hosted Node 24 Linux and macOS proof showing: one post-identity hook call; owner release while the waiter holds the opened descriptor; Root.open records path-mismatch; same-capability stat records not-found; create-only retry transitions from already-exists to created; waiter payload/bytes are exact; verifyStillHeld() succeeds; the opened handle closes; and the path is absent after release. Separately require the after-open replacement trace to reject with FsSafeError("path-mismatch"), observe same-capability present, close the opened handle, and preserve exact replacement and displaced-original bytes.

Keep the production owner boundary unchanged in readSidecarLockSnapshot, generic Root behavior fail-closed, raw-path snapshots unchanged, and all previous deterministic replacement/root-drift/probe-failure coverage. Require fresh exact-head Node/native/coverage/package/audit/analysis/security gates and no actionable review finding before merge.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane. and removed clawsweeper:human-review ClawSweeper automerge is paused for maintainer review rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Aug 31, 2026
@clawsweeper
clawsweeper Bot merged commit a80d8f6 into main Aug 31, 2026
31 checks passed
@steipete

Copy link
Copy Markdown
Contributor Author

Landed proof for exact head 5581c300829ba572d61fefd725989509a44dd1c2.

Local verification:

  • CI=1 pnpm check: 6,086 tests passed, 76 skipped; build, docs, filesystem-boundary lint, file-size budget, package, and API checks passed.
  • focused sidecar suites: 3 files, 49 tests passed.
  • security suite: 5 files, 78 tests passed.
  • compiled public-API proof: owner release after pathname identity admission produced Root path-mismatch, same-capability not-found, create-only waiter reacquisition, verified ownership, closed old handle, and final absence; a separate after-open replacement produced path-mismatch and preserved exact replacement and displaced-original bytes.
  • Codex autoreview at P1: clean; no accepted/actionable findings.
  • git diff --check: passed.

Exact-head hosted verification:

Merged by clawsweeper[bot] as a80d8f64d93986b35a2283f06265f359453b373c. Generic Root.open() remains fail-closed; only the sidecar ownership layer normalizes a mismatch after the same Root proves absence. No package release or publication was performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper:automerge Maintainer opted this ClawSweeper PR into bounded ClawSweeper-reviewed automerge merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant