fix(daemon): report the device claim retained by a failed close - #1647
Conversation
When close cannot confirm the device was released, it deliberately keeps the advisory claim (handing an unconfirmed device to the next session would be worse) but deletes the session record on the next line regardless, leaving a claim naming a session the daemon no longer tracks with no trace. Emit a warn diagnostic naming the device key and session, mirroring the open path's existing rollbackNewSessionClaim handling. Retention policy is unchanged.
8800e18 to
6b6bc01
Compare
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Reviewed exact head Not ready yet: this device-facing failed-close path has no required exact-head live evidence. Please demonstrate a disconnect/unavailable-target close where the daemon log contains |
The retention decision reads `platformCloseError ?? cleanupAggregate`, and the existing pair only drove the first input. A best-effort cleanup failure — a wedged perfetto stop, a dead helper — is the branch operators hit more often and reaches the same retention through a different value, so narrowing the diagnostic to the platform-close branch left every existing test green. Verified red against exactly that: gating the emit on `platformCloseError` fails this test alone, 28 others unaffected. Live evidence for the device-facing path is still outstanding; this closes the untested residual the review named, not that requirement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Rva4YGtSCAKJqH5PbpcCU
|
Untested residual closed at The residual. The retention decision reads Verified non-vacuous, and isolated to that branch. Gating the emit on One test, not three — I first tried the blunter mutation (dropping Still outstanding, and I cannot close it here: the live disconnect/unavailable-target reproduction showing Generated by Claude Code |
Live evidence — failed close retains and now reports the claim (exact head
|
|
Re-reviewed exact head |
|
Summary
When
closecannot confirm the device was released — the platform close threw, or a best-effort cleanup step failed — the daemon deliberately keeps the advisory device claim. That is correct: handing a device in an unknown state to the next session would be worse.But the session record is deleted on the very next line regardless, and nothing said so. The claim was left owned by a session name the daemon no longer knows about, reclaimable only by reopening under that exact name or by the daemon dying —
pruneDeadDeviceClaimsonly reaps claims whose owning process is gone, and the daemon is long-lived by design.It now emits a
device_claim_close_effects_unconfirmedwarn diagnostic carrying the device key and session name, mirroringrollbackNewSessionClaimon the open path, which already handles the structurally identical situation. Retention policy is unchanged — this makes an existing deliberate behaviour observable.Validation
Two new tests using the real
acquireAdvisoryDeviceClaim/inspectDeviceClaimswith per-test temp claim dirs — no DI seam, which CI forbids. Proven red without the fix:The diagnostics file is never created because zero events are emitted without the change — a maximally strong failure signal.
Step 2 of the plan (extending the thrown error's
hint) was deliberately skipped:deviceClaimBlockingErroris either a rethrown platform error or an aggregate built insession-teardown.ts, neither constructed in this file, andAGENTS.mdrequires preservinghint/diagnosticId/logPathwhen wrapping rather than inventing a wrapper.Residual risk — draft until live evidence
No live run of a genuinely failed close yet (needs a device disconnected mid-close), so the diagnostic's real-world shape is unverified.
Follow-up
The retained claim is still only recoverable by reopening under the same session name or by daemon death. A
device release --stale/--forcesurface would close that, and the payload added here (device key + session name) is exactly what it would consume. Related: #1320, whose read side already ships.Scope
2 files. Split out of #1639 per review. Local gates are currently unreliable on this machine due to a competing test run — pushed on GitHub CI's authority with maintainer agreement.