fix(daemon): let a retried open supersede the claim its aborted attempt abandoned - #2105
Conversation
Size Report
npm unpacked components
Startup median (7 runs, lower is better):
Top changed chunks:
Top changed packed files
|
|
Not ready. The open-retry supersession path is sound, but |
|
CI green on head e72f02c: 17/17 required checks pass, including the iOS Smoke lane whose |
Live device validation — the reported replay, on the shipped commitMy earlier comment claimed merge-ready on live evidence gathered from a stand-in replay Setup: dedicated throwaway iPhone 17 Pro / iOS 26.2 (created for this, deleted after), fixture app Pre-fix ( Shipped commit At The fixture app available locally is a month-old Release build, so the flow diverges at step 5 Cleanup: validation simulator deleted, scratch daemon stopped, the one stale claim its last attempt |
…pt abandoned An `open` that fails after preparation keeps its device claim: the effects it may have started are unproven, so releasing the device would hand an unknown state to the next session. Nothing recorded that the claim binds no session, so it stayed on disk for the daemon's whole life and every later open on that device failed with DEVICE_IN_USE naming a session that no longer exists. A `test --retries` run spent its whole retry budget on that conflict. Rolling an unowned claim back now marks it abandoned instead of leaving it untouched. Other processes still read a live claim, so the host-global fence is unchanged; the daemon that abandoned it supersedes it on its next acquire.
…ommand `acquireTransientDeviceClaim` treated any claim of this daemon as coverage, so an abandoned open claim let install/push/prepare/shutdown run without taking ownership and left the abandoned record behind. Only a claim that still holds the device covers a transient command; an abandoned one is superseded under the claim lock into the command's own transient claim, which its dispose releases.
e72f02c to
9f7459d
Compare
|
Code review is clean at This is not merge-ready yet: several exact-head checks remain in progress, and the new transient-exclusive recovery path has focused fixture proof but no exact-head live simulator replay. Attach the smallest practical aborted-open → transient command → claim cleanup evidence before merge. |
|
Rebased onto
Live proof of the exact hole you named, both sides under a single daemon (a keepalive session on Regression: Note that superseding also ends the fence: after the transient command disposes, the device has no Narration removed: the block comment on CI: 17/17 pass on |
|
Summary
--retrieswas worthless for a timed-out replay attempt: the failed attempt kept the simulator, soevery retry died at step 1 with
ios device <UDID> is owned by session "…:attempt-1".The claim, not the scheduler, was the hole. An
openthat fails after preparation deliberatelykeeps its claim — the effects it may have started are unproven, and releasing the device would hand
an unknown state to the next session. But nothing recorded that the retained claim binds no session,
so it stayed on disk for the daemon's whole life: no session could release it, no sweep could settle
it (its owner is alive), and every later
openof that device failed withDEVICE_IN_USEnaming asession
session listdoes not report andclose --sessioncannot reach. A replay-test retry isjust the loudest victim — a plain
agent-device openthat fails mid-launch blocked that device untilthe daemon was restarted.
A rollback that finds no session owning the claim now abandons it rather than leaving it
untouched:
live claim and still refuses the device;
can account for the effects in question (it owns the runner processes involved).
A
transient-exclusivecommand (install,push,prepare,boot,shutdown) treats a claim thisdaemon still holds as coverage and adds none of its own. An abandoned claim holds nothing, so it is
superseded into that command's own transient claim instead — otherwise the command would run unowned
and leave the abandoned record behind.
Retention stays exactly where it was earned.
session closestill retains a claim when teardownproved a resource is unsettled (
device_claim_close_effects_unconfirmed) — that device has knownoutstanding work, and the suite's
infrastructurestop is the designed response. Abandonment is forthe other case: a claim with nothing left alive to release it.
Before/after, same command, same simulator, same daemon (
test --retries 1, attempt 1 timed outmid-
open):Reported from the iOS smoke lane on #2100 CI
(run 33091927274, job 98587070677, head 15ec144): attempt-1's request log ends in
device_claim_open_effects_unconfirmedafter the runner connect exhausted its retries and theattempt timeout aborted the open, and the scheduler's
cleanupSessionwas a 0 ms no-op because nosession had ever been stored.
Two new diagnostics:
device_claim_open_effects_unconfirmednow carries the abandonoutcome, anddevice_claim_abandoned_superseded(info) records the takeover with the abandoned session name.Validation
examples/test-app/replays/gesture-pan-duration.aditself: dedicatedthrowaway device with the fixture app, real daemon over the repo CLI, host-global claims dir, at
the shipped head.
--timeout 3400puts the attempt deadline inside step 1'sopen, which is theCI shape. Pre-fix that reproduces the reported failure exactly (
… is owned by session "…:1-gesture-pan-duration:attempt-1"); on this head, 3/3 runs have attempt 2 run its ownopenand fail on its own timeout, with
device_claim_open_effects_unconfirmed {outcome: "abandoned"}followed by
device_claim_abandoned_supersededin attempt-2's request log. Full evidence and thecleanup record are in a PR comment. Validation simulator deleted, scratch daemon stopped.
session-device-claims.test.ts: a canceled attempt-1 through the realopenhandler and the realclaim store, then attempt-2 opening the same device. Reverting
session-open-execution.ts+device-claims.tsmakes it fail onretry.ok, with the sameDEVICE_IN_USEcause as CI.device-claims.test.ts: abandon → same-daemon supersede (and no orphan reconciliation), abandonedclaims still fencing a foreign state dir and a foreign owner, and the three abandon outcomes.
pnpm check:affected --rungreen (187 files / 1237 tests), pluscheck:layeringandcheck:fallow.Notes
Six files, all in the daemon claim lifecycle; no CLI, MCP, or help surface changes.
device statusstill reports an abandoned claim as
livewith its original owner session — accurate (it is stillfenced) but not self-explanatory; surfacing the abandoned state there is a follow-up, not a
prerequisite. This does not touch the underlying
gesture-pan-durationrunner flake that triggeredthe CI timeout; it makes the retry a real retry.