Skip to content

feat(daemon): the runtime owns its state and never the process - #3772

Open
thymikee wants to merge 6 commits into
refactor/host-environmentfrom
refactor/daemon-runtime-no-process-globals
Open

thymikee wants to merge 6 commits into
refactor/host-environmentfrom
refactor/daemon-runtime-no-process-globals

Conversation

@thymikee

@thymikee thymikee commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Summary

Part of #3747, stacked on #3763. Closes #3752.

A daemon runtime no longer assumes it owns the process.

  • Process ownership: startDaemonRuntime never calls process.exit and installs no process.on/once handler. src/daemon.ts owns signals, fatal errors and the exit code. A startup refusal rejects with details.exitCode (daemonStartupExitCode).
  • Shutdown: close() is idempotent: concurrent calls share one promise that resolves to a ShutdownReport (provider releases, runners, sessions, plugins). closed settles whoever closes (idle reap). Provider runtimes dispose exactly once; a startup that fails rolls back what it acquired. daemon stop --json still reports providerReleases.pending.
  • Per-runtime state: provider admission, interactor resolution, artifact/upload tracking, resumable uploads, materialized paths, session-open locks and request cancellation, plus the Apple runner's sessions, locks, recycle ledger and owner state, live in the runtime. Accepted sockets and HTTP handlers keep the runtime explicitly (AsyncResource.bind). The PNG worker is held by every runtime that uses it and stops with the last.
  • Not moved: the Apple runner host binding is a stateless function table (rebinding the same reference is a no-op).

Validation

pnpm check:affected --run --head 0b83711eb1 passed (exact commit). New tests: two runtimes in one process serve, close and keep state independently; idempotent close() and rollback; runner state per runtime; PNG worker refcount.
Live on iPhone 17e simulator (clean daemon, /private/tmp state dir): open, snapshot, close; graceful daemon stop handed the runner off (ios_runner_session_detached, runner kept alive) and a new daemon adopted it with no second xcodebuild; daemon stop --clean and device status left no claims or runners.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

thymikee and others added 4 commits October 17, 2026 01:35
… process

startDaemonRuntime no longer calls process.exit or installs process
handlers; src/daemon.ts owns signals, fatal errors and the exit code.
close() is idempotent and resolves to a ShutdownReport. A runtime that
fails during startup rolls back what it acquired.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… uploads and requests

Provider admission, interactor resolution, artifact and upload tracking,
resumable uploads, materialized paths, session-open locks and request
cancellation are owned by the runtime instance instead of module scope.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Apple runner's sessions, locks, recycle ledger and owner state belong
to the daemon runtime that composed them. The PNG worker is held by every
runtime that uses it and stops with the last one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.33 MB 5.33 MB +4.5 kB
Package (unpacked) 5.32 MB 5.33 MB +4.5 kB
Package (download) 1.60 MB 1.60 MB +1.4 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 41.5 ms 41.3 ms -0.2 ms
CLI --help 79.3 ms 78.5 ms -0.9 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 52 files

You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/daemon/src/server/http/daemon-runtime.ts Outdated
Comment thread packages/daemon/src/server/http/daemon-runtime-isolation.test.ts Outdated
Comment thread packages/daemon/src/server/http/daemon-runtime-close.test.ts
Comment thread packages/daemon/src/sessions/materialized-path-registry.ts
Comment thread packages/daemon/src/server/resumable-upload.ts Outdated
Comment thread src/daemon.ts Outdated
Comment thread packages/daemon/src/resources/artifact-tracking.ts
Comment thread packages/capture-kit/src/png-worker-client.ts
Comment thread packages/daemon/src/server/http/daemon-runtime-composition.test.ts Outdated
…moves temp files

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/daemon/src/server/http/daemon-runtime-close.test.ts
Comment thread packages/daemon/src/server/resumable-upload.ts
Comment thread packages/daemon/src/resources/artifact-tracking.ts
…one removal helper

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

Closing one runtime can still kill another runtime's Apple runner build, so this needs one more change before merge (reviewed at c047776). CI is green (12 checks, none failing), and I know of no other merge issue.

The Apple runner state in runner-artifact.ts is still process-wide. deviceStartStates (line 122) and runnerPrepProcessLedger (line 363) are module-level. When runtime A closes, stopAllIosRunnerSessions retires start admissions for every device in the process, then signals every prep child with no device filter (runner-disposal.ts:95). So A's close sends SIGINT, SIGTERM and SIGKILL to runtime B's in-flight xcodebuild build-for-testing, and B's open then fails with a teardown refusal. #3752 needs two runtimes in one process that close independently. The rule: every runner registry that a per-runtime sweep (close, abort-all, retire-all) enumerates must itself be per-runtime. Please move these two behind runnerRuntimeSlot, as runnerSessions already is, and audit the rest of runner/ with the same rule (path-keyed build locks and pure caches can stay). Please add a runner-session test that starts a prep in runtime B, stops all in runtime A, and asserts B's child survives. The isolation test cannot catch this today because its fixture has no Apple runner.

I found this by reading the code. I did not run two runtimes with an Apple runner. The PR body shows no artifacts for the live simulator run, and that run covers one runtime only, so per-runtime Apple isolation rests on runner-owner-state.test.ts. I also did not check whether anything in daemon composition reads Apple runner state before the late enterWith. A read there would land in the standalone store. The signal-to-exit-code glue in src/daemon.ts (75/78 on a startup refusal, 1 on a fatal error or failed close) has no direct test, and I did not run it.

Not blocking, and fine to take or leave: daemon-runtime.ts grows from 993 to 1099 lines, and AGENTS.md says files past 1,000 lines should be split before adding behavior, for example by moving RuntimeContext, createDaemonRuntimeStates and the ShutdownReport assembly beside server-shutdown.ts. Also, the same AsyncLocalStorage boilerplate (store, standalone fallback, getStore() ?? standalone, {run, enter, dispose}) appears in seven host-kit and daemon modules, Apple has a second mechanism in runnerRuntimeSlot, and best-effort rmSync is copied inline in resumable-upload.ts and materialized-path-registry.ts.

Could runnerRuntimeSlot become the single runtime-state owner in host-kit instead? One AsyncLocalStorage would hold a per-runtime store. runtimeSlot(key, create, dispose?) would return that runtime's slot and register its disposer, and createRuntimeStore() would return { run, dispose }. startDaemonRuntime would enter it once before composeDeps. That would delete RuntimeStateHandle, the six per-module storages, RuntimeContext.extend/enterWith and the DaemonRuntimeDeps.runtimeStates field. Each stateful module would shrink to one runtimeSlot call, and the Apple prep and start-admission registries would join through the same call. It needs two decisions first. The standalone fallback outside a runtime would live in that one module, and RuntimeStateHandle would move out of request-cancel.ts.

On the open review threads, all are addressed at this head except none still apply as live issues. These can be resolved: PNG/plugins/lock release in closeOnHost, isolation test cleanup, close test assertions, materialized-path dispose, resumable-upload dispose, shutdown error in daemon.ts, artifact-tracking dispose, releasePngWorker, composition test resolver, infoPath and lockPath assertions, removeBestEffort, all fixed at this head. The dispose ordering thread is benign: dispose runs after the servers close and in-flight work settles, and only a force-cut handler could still touch the temp dir, with its connection already gone.

Before merge, Apple start admissions and the prep-process ledger must be per runtime, with a regression test showing B's prep child survives A's stop-all.

This branch has not been deployed

No deployments
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.

1 participant