fix(ios): never signal a recycled runner pid from a stale lease - #1621
Conversation
A runner lease file can outlive its runner by days (SIGKILLed daemon), and pids get recycled: cleanupLeasedRunnerProcesses killed lease.runnerPid raw — group kill + direct kill + pkill -P — while only the lease OWNER pid had identity verification. After a pid-space wrap the tree kill lands on whatever process now holds the pid. - record runnerStartTime in the lease at construction (both the fresh-spawn and adoption sites go through buildRunnerLease) - verify the pid before the SIGTERM/SIGKILL tree kills: start-time match, or for legacy leases without one, a runner-shaped xcodebuild command line; otherwise skip the tree kill and emit ios_runner_lease_recycled_pid_skipped - the pattern-based xcodebuild pkill still runs unconditionally, so genuinely stray runner processes are still collected - adoption skips a recycled pid too: the adopted session's disposal would later signal it
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Not ready — two P1 process-safety gaps remain:
The iOS Smoke check is also red ( |
…y adoption The stale-lease cleanup verified the runner pid once and reused it for both SIGTERM and SIGKILL; the runner usually dies on the SIGTERM, and the pid can be recycled while the awaited xcodebuild sweep runs before the escalation. Each signal now resolves a freshly verified pid. Adoption skipped identity verification entirely for legacy leases without a recorded runnerStartTime, then re-stamped the lease with the live pid's start time — laundering a recycled pid into a strongly verified lease that disposal would later kill. Adoption now shares the disposal path's verification, including the runner-shaped command-line fallback for legacy leases.
|
Both P1s addressed in 3e128b2: P1 — verify-once across TERM→KILL: P1 — legacy-lease adoption bypass: the start-time check in iOS Smoke: agreed it reads as the known wait-capture flake; this push re-rolls it — will confirm it greens before calling this ready. |
|
The two prior P1s are fixed and their regressions are meaningful, but one P1 race remains in adoption. PID identity is verified, then No ready label yet. The PR is conflict-free and all completed checks are green; iOS/Android Smoke, Coverage, and FreeRange are still running. |
The uptime probe is the last await before the adopted lease re-stamps the pid with its live start time; the xcodebuild can exit and its pid be recycled during that network round-trip while the old port still answers. Re-verify after the probe — everything from there to the lease write is synchronous.
|
Fixed in 2ef6c0c: adoption now re-verifies |
|
Re-reviewed exact head |
|
* origin/main: chore: Update GitHub Sponsors usernames in FUNDING.yml fix: resolve Dependabot security alerts (callstack#1623) fix: update MCP registry namespace (callstack#1618) fix(ios): corroborate recorded tap outcomes (callstack#1605) fix(ios): never signal a recycled runner pid from a stale lease (callstack#1621) build: eliminate tsdown bundle warnings (callstack#1607) refactor(contracts): one viewport-root predicate for the whole repo (callstack#1613) refactor(contracts): name façade exports explicitly and retire the pin table (callstack#1614) refactor(ios): share one private-XCTest event bridge between gesture and text synthesis (callstack#1608) refactor(daemon): give the Maestro fallback and ambiguous-match details real types (callstack#1612) docs: clarify iOS drag synthesis profiles (callstack#1616) # Conflicts: # scripts/layering/facade-symbols.ts # src/commands/capture/screenshot.ts # src/commands/recording/index.ts
Part of #1596.
What the #1596 evidence actually shows
Investigating the three element-17/20/29 zero-action deaths refuted the issue's headline mechanism — the daemon replace path did not kill the calling command (full evidence posted on the issue). But the audit surfaced a real adjacent defect on the exact code path the issue suspected:
The defect
cleanupLeasedRunnerProcesseskillslease.runnerPidraw —kill(-pid)+kill(pid)+pkill -P pid— from an on-disk lease file. The lease owner pid gets start-time identity verification; the runner pid gets none. A SIGKILLed daemon leaves its lease behind for days, pids get recycled (this machine's pid space observably wrapped within hours under bench load), and the next daemon's stale-lease cleanup then tree-kills whatever innocent process — including its whole process group — now holds that pid.The fix
RunnerLeaserecordsrunnerStartTimeat construction (both the fresh-spawn and adoption sites already go throughbuildRunnerLease).xcodebuild … AgentDeviceRunnercommand line. Otherwise the tree kill is skipped and aios_runner_lease_recycled_pid_skippedwarn diagnostic is emitted.pkill -fruns unconditionally as before — genuinely stray runner processes are still collected; only the unverifiable raw-pid signal is withheld.tryAdoptRunnerSessionFromLease) skips a recycled pid too: the adopted session's later disposal would signal it.Tests
prepareRunnerLeaseForStartupwith a recording adapter: recycled pid skipped, matching pid killed, legacy lease trusts only runner-shaped commands, dead pid skipped — all asserting the xcodebuild pattern cleanup still runs.vitest(83/83 in touched files),typecheck,lintgreen.