Skip to content

feat(host): capture HostEnvironment once at entry points - #3763

Open
thymikee wants to merge 37 commits into
mainfrom
refactor/host-environment
Open

thymikee wants to merge 37 commits into
mainfrom
refactor/host-environment

Conversation

@thymikee

@thymikee thymikee commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

Part of #3747. Closes #3749.

HostEnvironment = { env, cwd } now has one owner. Entry points capture it once, and the daemon runtime, request scope and command spawning receive it instead of reading process. About 260 files change, most of them test call sites.

  • Type and capture: HostEnvironment is in @agent-device/kernel/source-value. captureHostEnvironment(process) is in @agent-device/host-kit/process. It copies the environment.
  • Entry points: src/bin.ts, src/cli.ts (runCli(argv, deps, host)), src/daemon.ts and createAgentDeviceClient(config, { host }) capture once. A client built without a host captures process and then reads only that host: its default transport, daemon launch, state dir and stateDir() all resolve from it.
  • Daemon: startDaemonRuntime requires host. DaemonRuntimeDepsInput.host replaces env. RequestRouterDeps.host and RequestExecutionScope.host carry it. Request handlers, claim writers, lease admission and the reapers in packages/daemon read configuration from that host explicitly (through RequestHandlerChainParams.host or the env their constructor receives), not from the ambient scope.
  • Command spawning: withCommandHost({ host, requestEnv }, fn) replaces withRequestCommandEnv. Spawned commands start from host.env. commandHostEnvironment() still gives platform and root code that host and throws outside one; it is meant for command execution only, and their owners (refactor(platforms): read host settings from HostEnvironment in platform and capture packages #3750, refactor: read host settings from HostEnvironment in root src and remaining packages #3751) convert the remaining readers to explicit hosts.
  • Explicit inputs: whichCmd, resolveUserPath, expandSessionPath, the device-claim functions, loadDaemonPolicy, launchDaemonProcess and kernel allowlist and simulator-set resolvers take env or cwd as parameters.
  • Done when: host-kit, kernel and daemon-contracts non-test sources have no process.env or process.cwd().

Behavior changes: a relative state dir without a cwd is refused (relative_state_dir_without_cwd; callers now pass one), and a relative AGENT_DEVICE_CLAIMS_DIR is refused (relative_claims_dir; the store is shared by every daemon on the host, so it used to depend on where one of them started). Suites that reach host-reading code outside a daemon request or the CLI bind a host with bindTestCommandHost() from test-kit; there is no global test setup.

The root src/ readers moved to the host in #3775, which merged into this branch. The platform and capture packages still read the process directly, for #3777 to migrate, so commandHostEnvironment() stays until it lands.

Validation

pnpm check:affected --run --head 9f3e58ffbb (exactCommit) passed, with current main and #3775 merged.

🤖 Generated with Claude Code

thymikee and others added 3 commits October 16, 2026 22:03
Add HostEnvironment to the kernel and captureHostEnvironment to host-kit.
The CLI, the daemon executable and the client factory capture it once;
startDaemonRuntime requires it and the request scope exposes it. Host-kit,
kernel and daemon-contracts no longer read the process environment or
working directory.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Platform packages may not read process; they take the environment and
working directory of the enclosing command host. Unit tests enter the live
process environment as their host.

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

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.38 MB 5.38 MB +5.6 kB
Package (unpacked) 5.38 MB 5.38 MB +5.6 kB
Package (download) 1.62 MB 1.62 MB +2.7 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.8 ms 42.0 ms +15.2 ms
CLI --help 77.2 ms 79.7 ms +2.5 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 178 files

You’re at about 91% 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 src/cli/commands/connection-runtime.ts Outdated
Comment thread packages/daemon/src/sessions/handlers/session-boot.ts Outdated
Comment thread packages/daemon/src/server/http/daemon-shutdown-claims.ts Outdated
Comment thread packages/daemon/src/sessions/session-lifecycle/internal/session-open-execution.ts Outdated
Comment thread src/cli/commands/device.ts Outdated
Comment thread packages/daemon/src/server/__tests__/request-execution-scope.test.ts Outdated
Comment thread src/daemon-entrypoint.test.ts
Comment thread scripts/print-daemon-state-dir.ts Outdated
Comment thread packages/host-kit/src/command.ts Outdated
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 39 files (changes from recent commits).

You’re at about 93% 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 src/cli/commands/connection.ts Outdated
Comment thread src/cli/commands/connection.ts Outdated
Comment thread packages/platform-android/src/app-deployment.ts
Comment thread packages/daemon-contracts/src/device-claim-paths.test.ts
…CLI env plumbing

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

Copy link
Copy Markdown
Member Author

The code needs no changes before merge, but the kernel-errors mutation checks are red and must pass first. I reviewed commit 5effcd7.

All five kernel-errors shards fail the Stryker dry run on one test, host-kit/src/code-signature.test.ts:149 ("the daemon source graph stamps the walker that produced it"). That test is unchanged and passes in the unit lanes. The shards passed in run 38965344815. The related-test set for these shards grew from 1372 to 1607 files, which pulled this test in. The new global setup file (https://github.com/callstack/agent-device/blob/5effcd7/src/__tests__/host-environment-setup.ts#L8) imports host-kit/command and so exec.ts and kernel/errors, so I think it caused that growth. I did not reproduce the failure in the sandbox. The walker probably resolves workspace symlinks outside the sandbox root, but I have not proven that. The "Affected decision-kernel mutants" check fails after the dry run ("13 report(s), expected 18"), and Smoke Tests was still running when I looked. No conflicts.

The next step is to remove the global aroundEach setup, or to make that test work in the sandbox. Re-run the kernel-errors shards after that.

Not blocking, and you can take or leave these. The setup file binds every unit test to the live process.env and process.cwd(), so no test can see host_environment_unscoped (exec.ts:140). Please bind createTestHostEnvironment only in suites that enter through the daemon or CLI, and add one test that calls a commandHostEnvironment() consumer outside a scope and expects that reason. Also, createAgentDeviceClient(config, { host }) at https://github.com/callstack/agent-device/blob/5effcd7/src/agent-device-client.ts#L89 uses host only in stateDir(), while the daemon client still reads process.env and process.cwd() (daemon-client-lifecycle.ts:97-140, 818). Either every resolution the client makes should read the same host, or deps.host can wait for #3751. Finally, RequestExecutionScope.host (https://github.com/callstack/agent-device/blob/5effcd7/packages/daemon/src/server/request-execution-scope.ts#L263) has no production reader except its own test, because daemon handlers use the ambient commandHostEnvironment() (19 call sites).

Would one channel per layer be simpler? host-kit would keep the ALS scope only for spawn env and the DEVELOPER_DIR overlay. Daemon handlers would read host from RequestExecutionScope or the runtime deps they already get. Platform code would take host from its existing host or tool-provider object, as AndroidAdbHost.environment does. That would remove the throwing ambient accessor and the test-wide shim, and a missing host would become a type error. #3750, #3751 and #3752 will copy whatever shape this PR sets. Could we settle in #3747 or an ADR whether HostEnvironment travels explicitly or ambiently before they land?

The open inline threads still apply. The two P2 threads on connection.ts (#3763 (comment) on the home path, #3763 (comment) on provider profile) and the P2 thread on bundletool lookup (#3763 (comment)) are still open. One lower-priority thread also applies: #3763 (comment).

@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 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 src/__tests__/host-environment-setup.ts Outdated
thymikee and others added 3 commits October 17, 2026 01:05
The global setup made every test related to every mutated source. Suites
that reach host-reading code through neither a daemon request nor the
CLI bind a host with bindTestCommandHost().

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

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

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

Copy link
Copy Markdown
Member Author

Pushed d522d257de: the global test setup is gone, so the related-test set no longer grows. Suites that need a host bind one with bindTestCommandHost() (test-kit); a new test pins host_environment_unscoped outside a host. The kernel-errors 3/5 mutation shard passes locally with 1371 related tests (main: 1369). Waiting on CI.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

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

Built to branch gh-pages at 2026-10-17 18:23 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@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 97 files (changes from recent commits).

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 src/__tests__/cli-capture.ts
@thymikee

Copy link
Copy Markdown
Member Author

Thanks for the PR. The host capture is a good direction, but one defect in the client needs a fix before merge. CI is green at d522d25 (23 checks, none failing), and there are no conflicts.

createAgentDeviceClient now accepts deps.host (or captures one at creation, line 89), and client.stateDir() resolves from host.env.AGENT_DEVICE_STATE_DIR and host.cwd (src/agent-device-client.ts#L215). Every command the same client sends goes through sendToDaemon, which still resolves the state dir from live process.env and process.cwd() (daemon-client-lifecycle.ts#L97, L107) and launches the daemon with process.env (line 818). Two inputs show the gap: createAgentDeviceClient({}, { host: { env: { AGENT_DEVICE_STATE_DIR: '/x' }, cwd } }), and setting process.env.AGENT_DEVICE_STATE_DIR (or calling chdir) after the client exists. Before this PR both reads were live and agreed. Now a public Node API client can report one state dir while it starts and talks to a daemon in another, so tooling that cleans or inspects client.stateDir() acts on the wrong daemon. The rule: everything one client reads must resolve from the same host. Either thread host through the transport factory into resolveClientSettings and startDaemon (explicit state dir, cwd, launch env), or drop deps.host and keep stateDir() on live process reads until #3751 moves the daemon client.

Not blocking, and you can take or leave these: RequestExecutionScope.host is set but nothing in production reads it, because daemon handlers call the ambient commandHostEnvironment() and the test only checks the field is the object passed in (request-execution-scope.ts:275); the MCP route runs inside the bin host scope but createClient calls createAgentDeviceClient(config) without deps.host, so it captures process again (src/mcp/command-tools.ts:251); ensureHarmonyToolchainPathConfigured writes env.PATH on the shared captured host, and captureHostEnvironment does not freeze its copy, so the Readonly type does not stop the write (packages/platform-harmonyos/src/hdc.ts:72); a relative AGENT_DEVICE_CLAIMS_DIR used to resolve against cwd and now throws relative_claims_dir, which the PR body and CHANGELOG do not mention, and the body also names a setup file that does not exist and says 178 files where the diff has 262 (device-claim-paths.ts:35); createTestHostEnvironment repeats captureHostEnvironment by hand (packages/test-kit/src/host-environment.ts:13); and the "request meta cwd or daemon cwd" fallback now reads inline process.cwd() in some daemon sites (screenshot-runtime.ts:326, record-runtime.ts:230/239, the session-script files, session-replay-transaction.ts:65) but commandHostEnvironment().cwd in others (session-perf-runtime.ts:149, session-open-execution.ts:432), so one source or a tracking line in #3751/#3752 would help.

Could the AsyncLocalStorage scope keep only what it already owns, which is spawn env (resolveSpawnEnv, commandDeveloperDir, exec trace), while handlers get the host explicitly? Daemon handlers would read scope.host or a host on their params, CLI ClientCommandHandlers would get ctx.host in the context they already receive, and platform modules would take env from their existing host ports, as Android does with AndroidAdbHost.environment. That would remove the throwing ambient getter and the 89 bindTestCommandHost suites, and make the dead scope.host field the real channel. Before #3750 and #3751 build on this, please decide in #3747 whether "bound HostEnvironment" means an ambient locator or explicit context fields.

I did not trace every public SDK export (artifacts, android-adb, limrun, ai-sdk) to prove none reaches commandHostEnvironment() outside a scope, where it would throw host_environment_unscoped. Unit tests cannot catch that, because 89 suites bind a live host. I also did not run code to check that the scope reaches HTTP connection callbacks and every daemon timer, though the request router rebinds per request. I did not run the tests, so the claim that cli-client-commands.test.ts fails without the harness binding is unverified. No live device run was asked for or checked.

This is reviewed at d522d25. Before merge, the client needs to resolve every state dir read from one host, and the scope question for #3750 and #3751 needs an answer.

createAgentDeviceClient's default transport, the daemon launch and the
CLI run's transport read the host the client was given, not the process.

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 11 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 src/daemon-client/daemon-client-lifecycle.ts Outdated
Comment thread src/__tests__/daemon-registration-owner.test.ts Outdated
Comment thread src/cli.ts Outdated
Comment thread src/daemon-client/daemon-client.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

Pushed 756cf4e012 for the client finding: createAgentDeviceClient's default transport is now createDaemonTransport(host), so state-dir resolution, remote and transport settings, the policy check, the daemon launch environment, the provider credential fingerprint and the developer dir all read the client's host. sendToDaemon keeps the released process-level default through a boundary wrapper; the CLI run builds its transport from the run's host. A new test (daemon-client-host.test.ts) gives the client a host that disagrees with process.env and checks that stateDir() and the settings the transport resolves agree.

Also taken from the non-blocking list: createTestHostEnvironment goes through captureHostEnvironment, and the PR body now names the relative_claims_dir refusal and no longer mentions the deleted setup file or the old file count. Not changed: the MCP route's createClient (its host is the same capture as the bin host), the Harmony PATH write (it deliberately changes the environment the daemon spawns with), and the inline process.cwd() fallbacks, which stay in the modules where an import would grow an eager closure; #3751/#3752 own migrating them.

On the ambient versus explicit question: the ambient commandHostEnvironment() exists because platform modules had no host to read and R13 forbids process there. I would like it to stay narrow, covering spawn env and the exec diagnostics, and have #3750 give platform modules their host through the ports they already own (as AndroidAdbHost.environment does), with handlers reading scope.host. That would let #3750/#3751 delete the accessor and the bindTestCommandHost suites as they go. It is your call, and I have asked in #3747 for the decision before they build on this.

thymikee and others added 2 commits October 17, 2026 07:39
…rectory

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

Copy link
Copy Markdown
Member Author

Thanks for the update. Since the review at d522d25 (#3763 (comment)), the client now resolves the policy against host.cwd, but one defect remains at f4616b9.

startDaemon passes env: settings.host.env but no cwd (daemon-client-lifecycle.ts#L832). launchDaemonProcess (daemon-registration-owner.ts#L262) spawns without options.cwd, so the daemon inherits process.cwd(). It then captures that as host.cwd (daemon.ts#L9) and resolves the policy at daemon-runtime.ts#L420, while the client resolves the same relative AGENT_DEVICE_DAEMON_POLICY against its own host.cwd (daemon-client-lifecycle.ts#L327). With createAgentDeviceClient({}, { host: { env: { AGENT_DEVICE_DAEMON_POLICY: 'policy.json' }, cwd: '/client/ws' } }), or a process.chdir() after the client exists, the two sides read different files. A Node SDK client then starts a daemon that exits with "cannot read policy JSON" or loads another file, and the client rejects it with DAEMON_POLICY_MISMATCH. Before this PR both sides read the live process cwd and agreed. The rule is that any relative value in the launch env must resolve the same way in the client and in the daemon. Please add cwd to the launchDaemonProcess input and pass cwd: settings.host.cwd to runCmdDetachedMonitored, which already supports options.cwd. That covers the policy path and any other cwd-relative setting the daemon reads. Please also add a regression where host.cwd differs from process.cwd() and the policy path is relative.

Not blocking, and fine to take or leave: CliDeps now has both sendToDaemon and an optional transportForHost (cli.ts#L70) plus resolveRunTransport. In production transportForHost is always set, so DEFAULT_CLI_DEPS.sendToDaemon and the process-capturing wrapper in daemon-client.ts have no production caller.

Could CliDeps take the transport only as transportForHost(host), defaulting to createDaemonTransport, with sendToDaemon and resolveRunTransport deleted? That would give a run one transport source, and launching with cwd: host.cwd fits in the same DaemonClientSettings.host owner. Nothing has to change first, since sendToDaemon is not a public export and tests can inject transportForHost: () => mock.

Of the open inline threads, the cubic-dev-ai thread on the policy path (#3763 (comment)) still applies until the spawn cwd is fixed. These no longer apply and you can resolve them: the createTestHostEnvironment() use in the registration test (#3763 (comment)), the runCli tests with a non-process host (#3763 (comment)), and the docstring that now names the #3751 upload exception (#3763 (comment)).

I did not run any test or daemon; the daemon cwd finding is traced from the spawn options and the entry capture, and I did not check whether other daemon reads also resolve relative env values against the inherited cwd. Remote artifact upload still reads process env and cwd, which I accepted as deferred to #3751 without tracing it. At f4616b9 Smoke Tests are queued, and Repo Guards & Compatibility and Coverage are still running with no failure yet. This change touches the daemon-client route that smoke runs use, so a failure there would likely be related. There are no conflicts. Before merge, please launch the daemon with cwd: settings.host.cwd, add the regression, and let the checks finish green.

thymikee and others added 4 commits October 17, 2026 09:13
…ment

* origin/main:
  test: move the daemon-composition tests off the daemon's testing exports (#3742)
  fix(android): ignore emulator log lines in -list-avds output (#3743)

# Conflicts:
#	packages/daemon/src/server/__tests__/request-router-fill-text-stdin.test.ts
#	packages/daemon/src/server/__tests__/request-router-macos-app-lease.test.ts
#	packages/daemon/src/server/__tests__/request-router-provider-credentials.test.ts
#	packages/daemon/src/server/__tests__/request-router-record-runtime-lock.test.ts
#	packages/daemon/src/server/__tests__/request-router-recording-health.test.ts
#	packages/daemon/src/server/__tests__/request-router-replay-scope.test.ts
#	packages/daemon/src/server/__tests__/request-router-screenshot.test.ts
#	packages/daemon/src/sessions/request-handler-chain-routing.test.ts
#	src/__tests__/daemon-composition/app-event-delivery.test.ts
#	src/__tests__/daemon-composition/gesture-admission-parity.test.ts
#	src/__tests__/daemon-composition/http-server-limrun-uploaded-apps.test.ts
#	src/__tests__/daemon-composition/request-router-android-perf.test.ts
#	src/__tests__/daemon-composition/request-router-android-snapshot-helper.test.ts
#	src/__tests__/daemon-composition/request-router-open-claim-recovery.test.ts
#	src/__tests__/daemon-composition/request-router-replay-test-ime.test.ts
…g test

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
thymikee and others added 2 commits October 17, 2026 12:42
…l call; default the lease provider cwd to the bound host

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

* origin/main:
  refactor(daemon): publish a single package entrypoint (#3781)
  fix(daemon-client): wait for an acknowledged quiesce instead of killing the daemon (#3780)
  fix(daemon): refuse leases for providers without a registered runtime (#3773)

# Conflicts:
#	packages/daemon/src/sessions/handlers/__tests__/lease.test.ts
#	src/daemon-client/daemon-client-ensure.ts
@thymikee

Copy link
Copy Markdown
Member Author

Pushed 916db0b9a4 (check:affected passed on it): current main merged (#3773 included), plus the reviewer's recommendations.

  • One transport per CLI run. runCliOnHost resolves the transport once and hands it to the remote-materialization client and the command client, so a run has a single ensure-once map.
  • One host per MCP tool call. createClient captures the host once and passes it to both createEnsuredDaemonTransport({ host }) and createAgentDeviceClient(config, { host }).
  • Lease provider cwd. lease_allocate, lease_heartbeat and lease_release fill the provider context's cwd from the bound host when the client sent none (test: lease.test.ts).
  • Threads. The three cubic threads are fixed and resolved.
  • Not done here: narrowing commandHostEnvironment() to command execution. Every non-test call site in packages/daemon, host-kit, kernel and daemon-contracts now reads an explicit host. The remaining sites are in the root src/ readers (refactor(host): read host settings from HostEnvironment in root src and remaining packages #3775) and the platform packages (refactor(platforms): read host settings from HostEnvironment in platform and capture packages #3777). Removing the export now would break those branches, so I will narrow it once both have converted.

@thymikee

Copy link
Copy Markdown
Member Author

The PR is ready at 916db0b. Both earlier cubic threads that the delta touched are fixed, and no blocking issues remain.

Not blocking, and fine to take or leave: the field doc at https://github.com/callstack/agent-device/blob/916db0b/packages/daemon/src/sessions/request-handler-chain.ts#L34 still lists only the session and record-trace routes, but the lease route now reads host too (line 157), so the doc could name it or just say host is the request's bound host. Also, no test pins the one-transport-per-run hoist or the single MCP host capture, so a runCli test with remoteConfig could assert transportForHost is called exactly once (today https://github.com/callstack/agent-device/blob/916db0b/src/daemon-client/daemon-client-host.test.ts#L62 only checks it gets the host).

Of the open inline threads, none are P1 or P2 and still apply, and no lower-priority ones apply. These do not apply and can be resolved: the unused host parameter in session-app-source-deployment.ts is gone since 225e0bd (#3763 (comment)); the idle-expiry test fixtures now use createTestHostEnvironment().env (#3763 (comment)); and the request-handler-chain.ts:34 rewording is superseded by the note above (#3763 (comment)).

CI is green at 916db0b: 23 checks, none failing. I did not run the tests myself, and I judged the lease.test.ts regression coverage from a read of the code before the change. I did not re-review the content of #3773, #3780 and #3781, only how their merge interacts with the host plumbing. Nothing else stands between this PR and a human merge.

…he bound host

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

Copy link
Copy Markdown
Member Author

Pushed 9a4e0812f4 (check:affected passed on it) for the 11:38Z recommendations.

  • Field doc. RequestHandlerChainParams.host now says it is the request's bound host, which the lease route reads too.
  • One transport per CLI run, pinned. cli-run-transport.test.ts runs a remote-materialization open through runCli and asserts transportForHost is called exactly once while the materialization client's lease_allocate and the command client's requests both go through it. Undoing the hoist makes it fail.
  • One host per MCP tool call, pinned. command-tools-daemon-lifecycle.test.ts asserts one captureHostEnvironment per tool call and that the host ensureDaemon receives is the captured object.
  • Threads. No inline thread is open on this PR.
  • Still pending: narrowing commandHostEnvironment(). Its remaining non-test call sites are the root src/ readers (refactor(host): read host settings from HostEnvironment in root src and remaining packages #3775) and the platform packages (refactor(platforms): read host settings from HostEnvironment in platform and capture packages #3777); I narrow the export once both have converted.

@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 3 files (changes from recent commits).

You’re at about 97% 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 src/__tests__/cli-run-transport.test.ts Outdated
thymikee and others added 4 commits October 17, 2026 14:10
…transport test

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

* origin/main:
  feat(provider-testmu): publish @agent-device/testmu on plugin contract v2 (#3770)
  docs(plugins): document plugin contract v2, the inventory and ADR 0034 (#3769)
  feat(daemon): daemon status lists the registered provider plugins (#3768)
  feat(plugins): plugin contract v2 and one provider registry (#3767)

# Conflicts:
#	packages/daemon/src/sessions/handlers/lease.ts
#	src/__tests__/cloud-connect-testmu.test.ts
#	src/cli/commands/connection.ts
#	src/cli/commands/daemon.ts
#	src/daemon-client/daemon-client-metadata.ts
#	src/daemon-client/daemon-client.ts
…ment

* origin/main:
  fix(ios-runner): stop wide sliders from wedging tree snapshots (#3776)
… leaving their shared prologue untouched

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

Copy link
Copy Markdown
Member Author

This PR is ready at c9aa2f2. The test changes since the earlier review (916db0b) look good, and the doc comment now says the request's bound host, which every route reads.

Not blocking: in src/tests/cli-run-transport.test.ts (https://github.com/callstack/agent-device/blob/c9aa2f2/src/__tests__/cli-run-transport.test.ts#L65) a failure other than 'exit' after lease_allocate still shows up only in an assertion message that already passes, the test never asserts the expected exit code, and fs.rmSync sits outside a finally block so a failing run leaks the temp workspace. You can take or leave these.

Both open inline threads from the earlier round are fixed at this commit and can be resolved: the doc comment on the request handler chain (#3763 (comment)) and the runCli error in the lease_allocate assertion (#3763 (comment)). The exit code is still not asserted, as noted above.

The Smoke Tests and Coverage checks are still queued or in progress, and none has failed. The new changes touch only tests and one doc comment, so they do not overlap with the smoke route. The full PR does change the CLI to daemon host plumbing that the smoke tests exercise, so a smoke failure would need a closer look. No conflicts are known. The PR needs those two checks to pass at c9aa2f2 before merge.

I read the code and did not run the new tests. That the vi.spyOn on the @agent-device/host-kit/process namespace takes effect for the dynamic import in command-tools.ts rests on the author's check:affected report and on the same spy pattern working in daemon-registration-owner.test.ts. Code merged from main still reads process.env by default, for example connectCommand's primeProviderConnection and listPlugins(process.env) in plugins.ts. That predates this change and is tracked in #3750 and #3751, and under the CLI it has the same values as the captured host. The request-router redaction test I asked for earlier is not in these changes; the PR only adds a createTestHostEnvironment() host fixture there.

thymikee and others added 2 commits October 17, 2026 15:47
…t cache its process already had

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ys remove its workspace

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 3 files (changes from recent commits).

You’re at about 97% 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 test/integration/provider-scenarios/harness.ts
thymikee and others added 3 commits October 17, 2026 15:57
…h of its env override

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* origin/main:
  fix(daemon): scope a transient device claim to the session that holds it (#3714)

# Conflicts:
#	packages/daemon/src/resources/device/__tests__/device-claim-admission.test.ts
#	packages/daemon/src/server/request-execution-scope.ts
… cleared it

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

Copy link
Copy Markdown
Member Author

Pushed d8a9bd40c0 (check:affected passed on it) for the 13:35Z recommendations, with current main (#3714 included).

thymikee and others added 2 commits October 17, 2026 16:51
…nd remaining packages (#3775)

* refactor(host): thread the client host through remote artifact preparation and materialization

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

* refactor(host): pass the composition's env to the platform runtime, diagnostics, toolchain and deployment hosts

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

* refactor(host): refuse a relative provider app path that names no working directory

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

* refactor(host): require the host's env and cwd where the CLI resolves options, binding and auth

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

* test(host): keep the upload client suite under its size ratchet

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

* refactor(host): pass the run's host to CLI command handlers

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

* test(host): bind host-taking commands once in a shared helper

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

* docs(changelog): resolveHubAppReference needs cwd for a relative app path

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

* test(host): give the new handler suites a host

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ment

* origin/main:
  fix: align replay, SDK, and session boundaries with ADRs (#3762)
  fix(daemon-client): document the drain timeout and tighten ensureDaemon follow-ups (#3783)

# Conflicts:
#	CHANGELOG.md
@thymikee

Copy link
Copy Markdown
Member Author

The code is ready for review at 870cf66. The earlier review (#3763 (comment)) found no problems, and the new #3775 changes in this round do not change that. The harness env ordering is now fixed, so the cubic P2 thread on harness.ts:119 no longer applies, so you can resolve it.

Not blocking, and you can take or leave these: the harness builds its gateway with env: process.env at https://github.com/callstack/agent-device/blob/870cf66/test/integration/provider-scenarios/harness.ts#L151 while its daemon host uses the scenario env, so passing input.host.env would match production (src/daemon-composition.ts:95) and let you drop the process-level PATH stub; the abandon call at https://github.com/callstack/agent-device/blob/870cf66/packages/daemon/src/resources/device/device-claim-admission.ts#L131 has no test that fails if it reverts to process.env, so the failed-release test could use a claims dir that differs from process.env and assert the record lands there; and the PR body still says non-owned root src keeps inline reads for #3751 and names validation commit 225e0bd, so it could mention #3775 and the current head.

The only open item is CI. Smoke Tests are queued or running with no failure yet. #3775 changes the root composition env and the CLI handler host, which the smoke route uses, so a smoke failure here would need a look. Smoke Tests must pass at 870cf66 before merge.

I did not run tests. I reviewed #3775's 98 files for new process.env or cwd reads, layer crossings and the resolveHubAppReference contract, but not its test call-site churn line by line. The changes merged from main (#3776, #3714, #3762) are upstream, and I checked them only where they meet host plumbing.

… host claims directory on a failed release

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 3 files (changes from recent commits).

You’re at about 97% 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 test/integration/provider-scenarios/ios-record-trace.test.ts Outdated
…rding scenario

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

Copy link
Copy Markdown
Member Author

The code in 9f3e58f is clean. The new commits since 870cf66 only add a daemon unit test, the provider-scenario harness changes, and a comment in the iOS record-trace scenario, and they fix the earlier gap. I read abandonDeviceClaim and writeOwnedDeviceClaim at this head and the new test matches what they do. I did not run the new device-claim-admission test or its mutation.

Smoke Tests is still running and has no failure yet. The new commits do not touch the smoke route, but the PR as a whole changes entry-point host plumbing that smoke exercises, so any failure there needs its own check. The PR has no known conflicts, and Smoke Tests must finish green before merge.

One open inline thread from another reviewer does not apply: the comment in ios-record-trace.test.ts lines 115-116 is accurate. #3775 merged into this branch, but the PATH and Swift-cache readers on this route still read the process environment at head (swift-cache.ts:140 and buildSwiftToolEnv() defaulting to process.env). #3777 migrates them, including src/platform-runtime-screen-recording-finalizer-host.ts. I only checked #3777 through its file list and PR body, not its contents. Please resolve it: #3763 (comment)

…ment

* origin/main:
  build(package): stop shipping Swift test sources (#3785)
  fix(scroll): take edge hints from a same-rect scrollable ancestor (#3784)
  docs(agents): write agent docs in ASD-STE100 and remove misleading docs (#3723)
  fix(daemon): retain cleanup locks and preserve healed snapshots (#3760)

# Conflicts:
#	packages/daemon/src/sessions/__tests__/replay-repair/session-replay-repair-record-exclusion.test.ts
@thymikee

Copy link
Copy Markdown
Member Author

Pushed cb17bb3051 (check:affected passed on it): current with main (#3760, #3762, #3784, #3785). The one conflict was a replay-repair test, where the new close call now passes the host. The cubic thread on the recording scenario's comment is fixed (779dcab21d) and resolved; no thread is open.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed cb17bb3. The code looks good, and I found no new problems since the earlier review at 9f3e58f. The range-diff shows the earlier commits unchanged, plus one test-comment commit (779dcab) and the merge with main.

The one merge conflict, in the replay-repair test, is resolved correctly: every handleCloseCommand call there now passes a test host, and the file reads no process.env or process.cwd. The cubic thread on the recording scenario is fixed in 779dcab and resolved.

All checks pass, and there are no conflicts. Nothing else blocks human review.

…orm and capture packages (#3777)

* refactor(capture-kit): read swift cache, app-log and PNG worker settings from the command host

The PNG worker starts with an empty environment and reports unavailability
through diagnostics instead of a process warning.

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

* refactor(provision-kit): read proxy settings from the command host

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

* refactor(react-native): read dev-server settings from the command host

The detached companion process starts with an explicit env and cwd, and its
process entry captures its host from its own spawn environment.

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

* fix(host): run the dev-server helpers inside the client's and the SDK's host

The helpers moved from process reads to the command host, so the released
agent-device/metro functions and the client bind one at their boundary.

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

* refactor(platform-apple): give detached runner and bridge children an explicit env and cwd

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

* refactor(platform-apple): read runner settings from the command host

The runner reaches the host through its port. The container bundle ids were
fixed at import time from the process environment; they are now resolved from
the host a caller passes in.

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

* fix(host): run the daemon clean script's runner lease cleanup inside its own host

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

* refactor(host): hand leaf modules their environment instead of importing the command host

The eager-closure budgets forbid the host-kit command import in the install
source download, the app-log file policy and the Maestro HTTP helper, so their
callers pass the environment they already hold. Client tests that change PATH
give the client a host instead.

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

* test(host): bind the command host in the install-artifact suites

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

* fix(host): answer review of the platform host environment change

Redaction patterns come from the command host, the maestro child reads its
own environment without a working-directory lookup, local ~ install sources
expand against the host environment, and the runner launch resolves its host
inside the try that closes its log descriptor.

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

* refactor(react-native): take the host explicitly instead of reading the command host

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

* refactor(platforms): pass the host explicitly where a platform package reads configuration

Android install artifacts read the environment from the Android adb host port,
Apple install artifacts take it as an option from the root deployment executor,
and the Apple runner names its host port member hostEnvironment.

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

* refactor(daemon): hand the app-log marker the request host's environment

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

* fix(capture-kit): report an unavailable PNG worker in the first diagnostics scope that sees it

A startup pre-warm runs outside any scope, where the event is dropped; it no
longer latches the one-time report.

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

* test(host): share the test command host binding through test-kit and test the runner's bound host behaviorally

test-kit no longer depends on capture-kit: its app-log live-handle alias is
gone (callers use the capture-kit constructor) and its keyboard fixture builds
its own snapshot state. capture-kit's tests take the helper from test-kit.

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

* refactor(capture-kit): expose the app-log live handle adapter through its own entry

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

* refactor(test-kit): build the test app-log handle without importing capture-kit

Replaces the capture-kit subpath entry, which grew the declared export surface.

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

* refactor(capture-kit): take the host explicitly for Swift cache and recording tooling

capture-kit no longer reads the command host, so its suites need no bound host
and its local test-host copy is gone; the root recording seams supply the host.
test-kit keeps depending on capture-kit again, and capture-kit does not depend on
test-kit.

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

* fix(capture-kit): report a worker that dies under a request in that request

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

* refactor(platform-android): take the install environment from the caller

The Android adb host port's environment is bound at module load, so a host
whose environment differs from the process's got the process's proxy settings
on a network install.

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

* test(platform-apple): pin the detached snapshot bridge's starting directory

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

* test(capture-kit): fail when the pre-warm starts no worker to kill

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

* test(host): keep the provider scenario host's Swift cache directory for scenarios that set it on the process

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

* fix(capture-kit): key the cached Swift helper paths on the host they were built for

The video validator is cached per Swift cache root and the overlay scripts per
working directory, so a second host does not reuse the first host's paths.

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

* test(host): give the Android recording scenarios their host env through the harness

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

* test(host): scenarios name their own host env; the harness no longer forwards the process's Swift cache

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

* test(host): drop the iOS recording scenario's process-env block now that capture-kit takes the host

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

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

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.

refactor(host-kit): capture HostEnvironment once at entry points

1 participant