Repository navigation
Description
Summary
The V2 managed background service inherits the environment of whichever reconnecting client wins service election. When several clients reconnect after a restart, crash, or version replacement, they race to spawn detached contenders with potentially different PATH values.
The winning environment becomes the process-global environment used by shell calls. As a result, the same command can work before a service replacement and fail afterward with command not found (observed with curl).
Reproduction
A bounded harness exercised the current Service.ensure path with two simultaneous reconnecting clients:
- Client A had a
PATHcontaining a fixturecurlexecutable. - Client B had a
PATHwithoutcurl. - Both called
Service.ensureconcurrently after the registration disappeared. - The elected service ran
/bin/sh -c curlusing its inherited environment. - The election was repeated 20 times.
Result:
{"found":10,"missing":10}The command outcome depended only on which client's contender won election.
Cause
packages/client/src/effect/service.tsspawns each contender with inherited environment viaspawn(command, args, { detached: true, stdio: "ignore" }).- A restart or service loss causes every attached client reconnect loop to call
Service.ensure, so contenders may originate from different terminal, IDE, or launcher environments. packages/core/src/shell.tsbuilds every shell environment from the elected server'sprocess.env.- Repository search found no production path that deletes, replaces, or mutates
PATH; this is environment replacement across server processes, not mutation within one server.
This is adjacent to the reconnect herd documented in #36285, but the user-visible failure here is nondeterministic process environment rather than contender resource overhead.
Expected behavior
Shell command resolution should remain deterministic across managed-service replacement and should not depend on which attached client wins a reconnect race.
Design options
- Canonical service environment: derive the service environment from the user's login shell on startup, independent of the spawning contender. Desktop already has a bounded login-shell environment probe that may be reusable after extraction.
- Persist a canonical local environment: choose and retain one non-secret environment contract across service replacements. This needs careful treatment of changing toolchains and secret values.
- Explicit client/session environment: pass the relevant execution environment through an owned client, session, or location boundary rather than relying on server-global
process.env. This is more precise but requires conflict semantics for multiple clients.
Questions
- Which boundary should own local command environment in the shared-service architecture?
- Should activated environments such as mise, direnv, Nix, or virtualenv follow the client/session, or should the service always use a canonical login-shell environment?
- What environment data is safe to persist or send over the local API?
- Should shell, PTY, MCP, LSP, formatter, and other subprocess producers share one environment policy?
Source discussion: https://slack.com/archives/C0BE69AHCQP/p1785009702750149
Requested by: @rekram1-node (Aiden Cline via Slack)
Activity
ogulcancelik commentedon Aug 16, 2026
@rekram1-node i’m hitting the same root cause from herdr. herdr gives each pane its identity through client-local variables such as
HERDR_PANE_IDandHERDR_SOCKET_PATH. with the shared service, a turn submitted from client b still runs with the environment of whichever client started the server.i reproduced this on
opencode2 v0.0.0-next-17444:an opencode setting could list which client environment variables should be forwarded. the TUI would read those values and attach them to each turn, with tool calls and subagents inheriting that turn’s environment. storing static values in server config would not work because each attached client can belong to a different herdr pane.
--standaloneworks around this, but shared-server clients still cannot identify their originating pane.neriousy commentedon Oct 21, 2026
Done in #42957: clients now send their terminal environment per session and the shell tool uses it (packages/core/src/shell.ts), so PATH no longer depends on which client wins service election. #43269 and #50907 also added a configurable, persisted service environment.