fix(host-kit): one async process-identity reader and one identity-checked stop escalation
Design
- Identity reader:
readProcessIdentities(pids, timeoutMs)andreadProcessIdentityFacts(pid)answer{startTime, command, zombie}off the event loop. They use two concurrentpsreads on POSIX and one CIM query on Windows. A pid that gets no answer reads as all-null, never as "absent". - Identity rule:
judgeProcessIdentity(expected, facts)returnsexited,reassigned,oursorunverified, and it is the whole rule for whether host facts name a recorded process.oursonly when every recorded claim (start time and/or command) was read and matches.reassignedfor a contradicted start time, or for a command mismatch when no start time was recorded.unverifiedfor everything else, including one confirmed claim beside an unread one.- The host-kit stop, the app-log inspect and the screen-recording marker evidence all use it.
- Caching: only the process's own identity is cached (
readCurrentOwnerIdentity, memoized once readable). A foreign identity is never cached. A process-lock wait re-reads a live owner's or guard holder's identity at most once per second for the same claim, and checkskill(pid,0)on every poll. - Escalation: within host-kit,
stopProcessAndConfirmExitis the single TERM→KILL path. It can open with SIGINT. It requires anoursverdict before the graceful signal and again before SIGKILL; anything else retains the process. Grace periods are set by each caller: reaper 1.5 s / 1 s, emulator 1.5 s / 1.5 s, app-log 2 s / 2 s. Two ladders outside host-kit keep their own escalation, and both check identity: the screen-recording set ladder (SIGINT, then SIGTERM, then SIGKILL, re-proving each marker per rung) anddaemon-process.tsstopDaemonProcess.
Summary
Fixes 2026-11-10 audit findings CONTR2-1, SRC2-2, CMD-3, CMD-4, CMD-6 and PROV2-5 (call site only).
- No synchronous
pson the daemon event loop: device-claim, lock-wait, recording, app-log and browser-fleet paths no longer block on it. - Reclaim guard (CMD-4): the guard file now records its holder. A guard whose holder is dead or whose pid was reused is reaped under a per-token marker. ADR 0030 is amended.
- One host-kit escalation (CMD-6):
stopPidsWithEscalationis removed. - Behavior changes:
- The reaper summary reports
unconfirmedandsurvivedinstead of the mislabelledownershipLost. A record whose pid is proven to belong to another lifetime is forgotten without a signal. - A record or marker whose recorded lifetime is not fully confirmed is retained, not signaled. That covers an unread start time or command, and a recorded start time shown running another command.
stopCompanionProcess(react-nativeclient-companion-tunnel.ts) now getsidentity-unverifiedin two cases: a recorded start time with a command mismatch, and a companion whose command line the host hides (the #3540 elevated case). It throws throughcompanionStopFailureand keeps its state; it does not signal.- The Android emulator focus helper (osascript) is best effort. A failure to start it emits
android_emulator_focus_return_failedand never fails the launch.
- The reaper summary reports
- Deferred: ARUN2-1 and ARUN2-5 change runner and device teardown timing, so they need a device run.
30 files changed.
Validation
- Tested SHA
778aa6e(check:affected --run,exactCommit=true): format, lint, typecheck, layering, fallow and build pass. Focused host-kit, app-log, screen-recording, emulator and react-native companion tests pass (522). integration-node(local): 2smoke-daemon-cleantests fail because the container's PID 1 does not reap the killed daemon, which stays a zombie. The daemon stop code is unchanged here.- Device runs: two live runs were posted. The iOS simulator record stop and app-log stop, and the Android cold-boot abort, at
bd15e4a(issuecomment-6777371512). The Android cold-boot cancel and the-avdtoken check at9f48ee2(issuecomment-6778854131). Both predate the stricter identity rule in778aa6e. - CI obligations: the full unit suite and the Android lane, because the emulator terminate path uses the shared escalation.
- Risk: the reaper forgets records whose pid is proven reused; an elevated companion on Windows is retained and reported, never signaled.
Generated by Claude Code
<a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1796921043&installation_model_id=447842&pr_number=4359&ref=codesmith_pr_footer&repository=callstack%2Fagent-device&return_to=https%3A%2F%2Fgithub.com%2Fcallstack%2Fagent-device%2Fpull%2F4359&signature=f672fb3630ba26d7e6468b7356f6af3c59cf9f56801a9473879c025372e1f33b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28">
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
<a href="https://www.cubic.dev/action/auto-fix/pr/callstack/agent-device/4359?returnTo=https%3A%2F%2Fgithub.com%2Fcallstack%2Fagent-device%2Fpull%2F4359&source=description" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Turn on auto-fix" src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg">