anomalyco / anomalyco/opencode
service: make subprocess PATH deterministic across reconnect election
@rekram1-node is already working on this.
Since Jul 25, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
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)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.