Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(daemon): take a foreign device claim the device's own reboot invalidated - #2570

Open
thymikee wants to merge 2 commits into
t3code/2538-device-boot-observationfrom
t3code/fix-2538-quality-review
Open

fix(daemon): take a foreign device claim the device's own reboot invalidated#2570
thymikee wants to merge 2 commits into
t3code/2538-device-boot-observationfrom
t3code/fix-2538-quality-review

Conversation

@thymikee

@thymikee thymikee commented Sep 13, 2026

Copy link
Copy Markdown
Member

Summary

open now settles a foreign device claim that the device's own reboot invalidated. Claims outlive the device they describe: shutting a Simulator or emulator down takes its app process, runner, and accessibility connection with it, while the claiming daemon and claim file stay healthy. Every later open refused with DEVICE_IN_USE, naming an owner with nothing left to close.

open asks the device when its current boot began, releases a claim predating that through the same exact-owner reconciliation device release --stale uses, and reports the release in warnings. The bound is the last open its owner made, renewed before a reopen touches the device, so a session cannot lose a device it is coming back to. The probe runs only for a conflicting foreign claim, after owner-liveness has answered.

Part 3 of 3 for #2538, on #2575 and #2574. 847 gross lines.

Closes #2538

Validation

Tested at 512736caf4. pnpm check:affected --run passes format, lint, typecheck, layering, fallow, build, provider and node integration, wire-compat, command docs, and related vitest (1,985 tests). mutation-model fails identically at base ab3d11e069 locally, where CI is green.

Live, two state dirs on one simulator: a pre-reboot foreign open returned DEVICE_IN_USE; after simctl shutdown and boot the owner's own open succeeded, so the next foreign open still returned DEVICE_IN_USE; rebooting again without an owner reopen let a foreign open take the device and say so, and an owner reopen whose claim that foreign session had taken was refused, naming it. Experiment device and sessions cleaned.

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.52 MB 4.52 MB +4.5 kB
Package (unpacked) 4.52 MB 4.52 MB +4.5 kB
Package (download) 1.33 MB 1.33 MB +1.7 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.4 ms 28.6 ms +0.2 ms
CLI --help 78.3 ms 77.5 ms -0.8 ms

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-2570/

Built to branch gh-pages at 2026-09-14 02:50 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@thymikee

Copy link
Copy Markdown
Member Author

Two ownership cases still need fixing at f0ae9d7.

In device-claim-reboot.ts:35, the original claim timestamp remains the only cutoff even after its owner successfully reopens the app after reboot. The existing-session open path preserves that claim, so a later foreign open can reclaim a device that the original session is actively using and clean up its resources. Renew or validate ownership against the current boot when the owner resumes, and cover owner reopen followed by foreign open.

In packages/platform-android/src/device-boot.ts:18, subtracting sampled uptime from the time the ADB response arrives moves the inferred boot forward by response latency. Shortly after boot, this can make a valid newer claim look stale without another reboot. Use a conservative bound from before the probe and cover a delayed response.

The diff is also 1,442 gross lines, above the repository’s 1,000-line PR limit; split the behavior-preserving extraction from the takeover change. The current smoke failure is the automation-longpress scroll assertion seen on #2565 before its successful retry, so it appears unrelated to these changes.

@thymikee
thymikee force-pushed the t3code/fix-2538-quality-review branch 2 times, most recently from 21db6ff to 0dd2527 Compare September 13, 2026 20:49
@thymikee
thymikee changed the base branch from main to t3code/2538-device-boot-observation September 13, 2026 20:49
@thymikee
thymikee added this pull request to stack #2576 September 13, 2026 20:50
@thymikee

Copy link
Copy Markdown
Member Author

Both ownership cases are fixed at 0dd2527678.

The bound is now the instant the owner last vouched for the device, not the first write. resolveExistingClaim renews updatedAtMs on the held path, and rebootedDeviceClaim compares the observed boot against that stamp, so an owner that reopened its app after the reboot keeps the device and its resources. Covered by an owner that reopened its app after the reboot keeps the device in src/daemon/__tests__/device-claim-settlement.test.ts and by a claim its owner renewed after the boot describes that boot in src/daemon/__tests__/device-claim-reboot.test.ts.

Android now samples the host clock before the adb round trip, so probeStartedAtMs - uptime is a bound never later than the real boot instant: latency can only push the answer earlier, which cannot make a newer claim look stale. Covered by a slow uptime answer cannot move the boot instant past the moment the probe began.

The diff is split as stack #2576: #2574 is the behavior-preserving claim-settlement move (772 gross), #2575 is the boot-observation contract, probes, and hub (489 gross), and this PR is the takeover alone at 957 gross lines.

@thymikee

Copy link
Copy Markdown
Member Author

The Android timing fix and PR split resolve those two points, but the owner-reopen bug remains at 0dd2527. renewHeldClaim only runs through acquireDeviceClaim; the existing-session open path in session-open.ts:162–243 still calls completeOpenCommand directly and never renews the claim. The new regression calls acquireDeviceClaim twice, so it does not exercise the reported production path. Wire renewal into a successful existing-session reopen and cover that routed flow followed by a foreign open, with live confirmation. Checks are green; this remains a code blocker.

@thymikee

Copy link
Copy Markdown
Member Author

Renewal now rides the production reopen at 2f8cbfec72.

renewDeviceClaim is the claim-side write, and completeOpenCommand calls it for a successful existing-session open, which is the branch session-open.ts reaches without ever acquiring a claim. The acquire-path stamp stays for the case that does go through acquisition. All three ownership-matched claim writes (clear, abandon, renew) now share one writeOwnedDeviceClaim seam under the claim lock.

Routed coverage: open renews the claim of an existing session that reopened its app after a reboot in src/daemon/__tests__/request-router-open.test.ts drives two routed opens for one session, asserts the stored updatedAtMs moved, then opens a second workspace with the boot observed between the two stamps and must get DEVICE_IN_USE with the claim still naming the owner.

Live on a fresh iPhone 17 Pro simulator, two state dirs and one device, Safari as the app:

  • pre-reboot foreign openDEVICE_IN_USE for session ad-owner;
  • simctl shutdown + boot, then owner open → succeeded, claim went createdAt 00:43:21Z / updatedAt 00:44:04Z;
  • foreign open again → DEVICE_IN_USE. That is the case that used to take the device.
  • rebooting once more with no owner reopen → foreign open succeeded with Took the device from session "ad-owner" …, so the release still happens when the owner really is gone.

Sessions closed, daemons stopped, state dirs removed, simulator deleted, device status --stale reports no local claims.

@thymikee

Copy link
Copy Markdown
Member Author

The sequential owner-reopen case is now fixed and has the requested live evidence. One ownership race remains at session-open-execution.ts:245: openApplication runs before renewal, so a foreign daemon can take the still-preboot claim while the owner is reopening. renewDeviceClaim then returns ownership-changed or absent, but the result is ignored and the old owner still reports success. The request execution locks are local to each daemon, so they do not serialize this case. Establish ownership across reopen and renewal, handle lost ownership, and cover a foreign takeover interleaved with owner reopen.

The layer is also back over the 1,000-line budget: 848 additions plus 211 deletions, or 1,059 gross lines. Reduce it by at least 59 lines or move the behavior-preserving extraction into the lower layer. CI is green at 2f8cbfe; these remain the blockers.

@thymikee
thymikee force-pushed the t3code/fix-2538-quality-review branch from 2f8cbfe to 512736c Compare September 14, 2026 02:50
@thymikee

Copy link
Copy Markdown
Member Author

Ownership now spans the reopen at 512736caf4, and the layer is back under budget at 787 additions and 60 deletions.

Renewal moved ahead of all device work: session-open.ts renews the existing session's claim right after argument validation, before prepareOpenCommandDetails can boot anything, and completeOpenCommand no longer writes a claim after launching. renewDeviceClaim answers with the record that took the device, so losing ownership is a typed outcome rather than an ignored string: a lost renewal ends the reopen with the ordinary DEVICE_IN_USE conflict response, or a claim-lost-during-reopen refusal when the claim is gone, and the app is never launched on a device this session no longer owns.

Coverage: an owner reopen that lost the device mid-flight reports the loss instead of launching in src/daemon/__tests__/request-router-open.test.ts takes the owner's claim away with a foreign one between two routed opens and asserts the refusal names the new owner and leaves that claim in place. The settlement cases are now one file, which also removed the duplicated copies of six acquire tests this layer had been adding back to device-claims.test.ts — that is where the 172 lines came from rather than moving the extraction again.

Live, two state dirs on one simulator: owner open, then simctl shutdown and boot, then the foreign open took the device; the owner's next open returned DEVICE_IN_USE … owned by session "race-b" … instead of launching over it. Sessions closed, daemons stopped, state dirs and experiment simulator removed, device status --stale clean.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A session claim on a device that was shut down and rebooted since the claim should not block open with DEVICE_IN_USE

1 participant