Skip to content

fix: persist daemon-owned child process records - #2019

Merged
thymikee merged 2 commits into
mainfrom
fix/1882-owned-child-process-record
Aug 25, 2026
Merged

fix: persist daemon-owned child process records#2019
thymikee merged 2 commits into
mainfrom
fix/1882-owned-child-process-record

Conversation

@thymikee

@thymikee thymikee commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

Closes #1882.

Validation

  • pnpm check:affected --run — all runnable checks passed: format, lint, typecheck, layering (including durable-owner rules), fallow, build, Node integration (92 passed / 9 expected skips), and related Vitest (310 files, 2,090 tests). The first sandboxed attempt stopped only because its leak-model preflight could not invoke /bin/ps (spawnSync ps EPERM); the identical gate passed with host process visibility.
  • Focused follow-up: 3 files, 32 tests passed for Apple simulator recording, managed web provider, and the durable record store.
  • Planted-red evidence: temporarily removing the post-exec identity refresh made publishes the post-exec simulator identity after readiness fail; temporarily removing semantic-success fencing made the exit-0 {success:false} close regression observe clear being called. Existing web: agent-browser Chrome fleets leak and can spin at 100% CPU for days — add idle lifecycle/reaper #1109/iOS simulator recorder leaks on session/daemon teardown while recording is active (0-byte recording.mp4, "Host recording is already in progress") #1324 record-reading and publication tests also have planted violations and fail closed.
  • macOS-host native evidence is now available. On a booted iPhone 17 Pro simulator, the built CLI record start --scope device persisted the live CoreSimulator simctl io ... recordVideo identity (the exact ps command matched it); record stop exited successfully, produced the MP4 and gesture telemetry, cleared owned-processes.json, and left the recorder PID dead.
  • Native crash/startup-reap evidence: after a real recording, killing the daemon left the recorded CoreSimulator PID and durable record in place; the next daemon startup through devices read the record, reaped the recorder, and cleared the record. The crashed session was then cleaned up.
  • pnpm vitest run test/integration/provider-scenarios/ios-record-trace.test.ts — 2 passed; the fixture starts a real child through the injected simulator transport and verifies record write/clear.
  • node --experimental-strip-types --test test/integration/daemon-leak-oracle-cli.test.ts — passed, including the exact daemon-owned process record case.
  • Adversarial review attempt: the approved claude -p invocation was made; the sandbox reported not logged in, and bounded Keychain-backed retries produced no output and were stopped. No Claude findings were available; local diff audit, maintainer findings, and planted-red checks were completed.

Design and size accounting

Against origin/main at 07217c53f, the diff is 30 files, +1,733/-226. The regenerated size report is +7.5 kB JS/unpacked, +2.3 kB gzip, and +2.2 kB tarball.

  • Runtime/package paths: +906/-165 across 16 files (net +741). The durable record store is 228 lines and the generic reaper is 217 lines (+445 total) for atomic versioned daemon/session state, path-safe validation, PID/start-time/command fencing, purpose filtering, escalation, and startup cleanup. The managed-web owner adds 185 lines for process-table observation at the provider lifecycle seam; provider wiring is +93/-12 and lifecycle extraction is +43/-136. The remaining +140/-17 is typed contract, daemon wiring, and Apple host/descriptor integration.
  • Test/fixture/oracle support: +827/-61 across 14 files, including the device-free model/oracle rules, CLI exact-record coverage, provider scenarios, fixture wrapper-to-exec proof, and semantic-close regression.
  • A smaller owner-specific implementation was rejected because it would duplicate atomic file format, validation, startup reaping, escalation, and daemon/session scope semantics between Apple and web. The shared record/reaper owns only durable identity mechanics; Apple retains the typed AppleRecordingDescriptor and purpose-owned live matcher, while web retains the narrow process-observation owner. A generic purpose/command matcher would type-erase semantic identity and caused the original simulator P0.

Evidence gaps

Touched-file count: 30. Ready for review; do not merge.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.41 MB 2.42 MB +7.5 kB
JS gzip 807.5 kB 809.8 kB +2.3 kB
npm tarball 932.5 kB 934.7 kB +2.2 kB
npm unpacked 3.24 MB 3.24 MB +7.5 kB

npm unpacked components

Component Base Current Diff
JS / dist source 2.56 MB 2.57 MB +7.5 kB
Apple runner source/project 570.6 kB 570.6 kB 0 B
macOS helper source 54.5 kB 54.5 kB 0 B
Android helper artifacts 0 B 0 B 0 B
Other package files 45.2 kB 45.2 kB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.8 ms 26.9 ms +0.1 ms
CLI --help 80.6 ms 79.3 ms -1.2 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/internal/daemon.js +3.0 kB +944 B
dist/src/runtime4.js +648 B +135 B

Top changed packed files

Packed file Base Current Diff
dist/src/internal/daemon.js 103.9 kB 106.8 kB +3.0 kB
dist/src/agent-browser-tool.js 7.1 kB 10.0 kB +2.9 kB
dist/src/agent-browser-provider.js 17.0 kB 17.9 kB +822 B
dist/src/runtime4.js 45.4 kB 46.1 kB +648 B
dist/src/platform-runtime-operation-host.js 17.5 kB 17.6 kB +119 B
dist/src/platform-runtime.js 11.6 kB 11.7 kB +32 B
dist/src/platform-runtime-screen-recording-apple-simulator-host.js 4.9 kB 4.9 kB +16 B

@thymikee

Copy link
Copy Markdown
Member Author

Changes requested on 9fc6b02.

  1. P0 — normal iOS simulator record stop is broken. Exact-head source evidence on iPhone 17 Pro: record start succeeded and persisted /bin/bash …/usr/bin/simctl io …, but the live PID exec'd to CoreSimulator's /Library/Developer/…/bin/simctl io …; record stop then failed with simctl recordVideo process ownership changed before cleanup (mt7peeyz-e24f3d72). Identity is captured pre-exec in packages/platform-apple/src/recording/runtime.ts:92-102, while src/platform-runtime-screen-recording-apple-simulator-host.ts:22-34 and the generic reaper reject the post-exec identity. Use a typed/purpose-owned matcher shared by live cleanup and startup recovery, or capture stable post-exec identity. Add a planted wrapper→exec regression plus real CLI start/stop and crash/startup-reap evidence.

  2. P1 — web close can erase the sole durable record on semantic failure. agent-browser-provider.ts:273-280 finalizes before unwrapAgentBrowserJson; canClear... treats exit 0 as success, so an exit-0 {success:false} close clears the record while the browser may remain. Clear only after semantic JSON success (or re-record identities), with a regression for that case.

  3. Required size/design accounting. This is +1,648/-214 total; production net is roughly 900–1,000 lines and Size CI reports npm unpacked +7.1 kB. Please itemize justified retained growth and explain why the smaller owner-specific design was rejected: reuse the existing typed simctl capture descriptor/matcher for simulator recording and keep a narrow web-owned durable record. The current generic {purpose:string, command} layer type-erases semantic identity and already caused finding 1.

Focused exact-head build and 67 tests passed; they do not cover the two failure modes above. CI is still pending, so no readiness label.

@thymikee
thymikee force-pushed the fix/1882-owned-child-process-record branch from 9fc6b02 to 2fc31c2 Compare August 25, 2026 06:17
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the three requested review points on 2fc31c22f:

  1. iOS simulator recorder leaks on session/daemon teardown while recording is active (0-byte recording.mp4, "Host recording is already in progress") #1324 / simulator P0: the Apple simulator host now refreshes the root identity after recording output is ready, so the durable record stores CoreSimulator's post-xcrun simctl command. The planted wrapper→exec test goes red without that refresh. A real macOS-host simulator run now passes record startrecord stop, clears the record, and leaves no recorder PID; a real daemon-kill → next-startup run also reaped the recorder and cleared the record.
  2. Web semantic failure: record clearing now requires semantic JSON success in addition to exit 0 and command completion. The planted exit-0 {success:false} close regression goes red without the guard and passes with it.
  3. Design/size accounting: the PR body now itemizes the current rebased diff (+1,733/-226 across 30 files), runtime versus test support, the +7.3 kB unpacked/+2.2 kB gzip size report, and why shared durable mechanics plus owner-specific Apple/web identity seams are safer than a type-erased generic matcher.

The branch is rebased onto current main; local pnpm check:affected --run is fully green (including 310 related Vitest files / 2,090 tests). GitHub checks for the new head are running now.

@thymikee

Copy link
Copy Markdown
Member Author

CI follow-up: the first iOS smoke attempt hit an unrelated live-runner TEXT_INPUT_COMMIT_NOT_OBSERVED failure in the existing smoke:form-input scenario (empty xcodebuild output); no text-input/XCTest code changed here. Rerunning the failed job passed end to end in 13m47s. All PR checks are now green, including Android, iOS, macOS, Linux, coverage, CodeQL, size, package, and repo guards. The regenerated size report is +7.5 kB JS/unpacked, +2.3 kB gzip, and +2.2 kB tarball; the PR body has the corrected figures.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed 2fc31c22: clean and ready for human review.

The two prior correctness blockers are resolved at their owning seams: Apple re-reads the recorder root identity after xcrun execs into simctl, so explicit stop and startup reaping use the stable post-exec identity; web durable ownership is cleared only after semantic close success, so exit-0 { success: false } retains the record. PID/start-time/command fencing remains fail-closed, and purpose-specific identity stays in the platform owners rather than leaking into the shared record layer.

The large-change threshold is triggered (+7.5 kB unpacked; roughly 741 net runtime lines), but the growth is itemized and the shared durable-record/reaper construction path is materially smaller and safer than duplicating lifecycle logic. Current checks are all green, planted-red regression evidence is recorded, and the PR includes exact-head macOS Simulator start/stop plus daemon-restart reaping evidence.

No actionable code findings. Residual coverage is limited to managed-web/provider and physical-device paths not independently reproduced here.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 25, 2026
@thymikee
thymikee merged commit 777c7af into main Aug 25, 2026
19 of 20 checks passed
@thymikee
thymikee deleted the fix/1882-owned-child-process-record branch August 25, 2026 07:23
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-25 07:23 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

daemon: record owned child pids so leak detection reads a record instead of reconstructing it from ps

1 participant