Environment variable pass-through
The daemon runs as a background process and typically has a stripped-down environment. Your terminal has the full environment set up by your shell profile. There are two ways env vars reach shell subprocesses spawned by the agent.
zerocode forwarding (automatic)
When zerocode connects it captures its own process environment and sends it to
the daemon as part of the initialize handshake. The daemon stores that
snapshot in TuiRegistry keyed by zerocode’s unique tui_id. When you open a
new chat session (session/new), the daemon looks up the snapshot registered
by the calling connection itself, never one named by the request, and clones
it into the agent’s ShellTool. That clone is then overlaid on top of
the safe-env baseline for every shell subprocess the agent spawns:
cmd.env_clear()
→ Layer 1: SAFE_ENV_VARS + shell_env_passthrough (from daemon process)
→ Layer 2: zerocode's env snapshot (wins on conflict)
zerocode vars win on conflict: your PATH, HOME, and credential sockets
take precedence over whatever the daemon inherited. No configuration required.
This is why SSH_AUTH_SOCK works when you run zerocode from a terminal that
has an ssh-agent running, even if the daemon was started as a service with no
agent:
sh
# Terminal has SSH_AUTH_SOCK set by ssh-agent or a hardware token (YubiKey, etc.)
echo $SSH_AUTH_SOCK
# /run/user/1000/gnupg/S.gpg-agent.ssh
# Daemon was started as a systemd service, with no SSH_AUTH_SOCK in its env.
# zerocode forwards its env at connect time, so any shell command the agent
# runs (git push, ssh, gpg-sign) gets SSH_AUTH_SOCK from your terminal.
zerocode sends its full environment on every connection, but the daemon keeps
the snapshot only where it is trusted: an operator-level principal on a local
connection (the Unix socket or the Windows named pipe), which is the local IDE
flow described above. On a WSS connection the snapshot describes the client’s
host rather than the daemon’s, and a roster principal with a permission
profile must not choose what runs as the daemon account, so in both cases the
daemon drops the snapshot at initialize, logs that it did, and the session’s
shell subprocesses see only Layer 1. Use shell_env_passthrough on the risk
profile to supply specific variables from the daemon’s own environment there.
Multiple connected clients: no cross-session clobbering
Each zerocode instance gets a unique tui_id (tui_ + 8 random hex chars).
The registry is a HashMap<tui_id → TuiEntry>, and entries are completely
independent:
TuiRegistry
├── "tui_a1b2c3d4" → { env: { PATH: "/home/alice/…", VIRTUAL_ENV: "…" } }
├── "tui_beef0042" → { env: { PATH: "/home/bob/…" } }
└── "tui_cafe1234" → { env: { PATH: "/opt/pyenv/…" } }
When zerocode tui_a1b2c3d4 opens a session, only its env snapshot is
cloned and used. The other clients’ envs are never touched. Concretely:
| Scenario | Result |
|---|---|
Two clients open from different shells with different PATHs | Each session gets its own PATH; neither affects the other |
Client A has VIRTUAL_ENV set; Client B does not | Only sessions from Client A see VIRTUAL_ENV |
| Client A disconnects while Client B’s session is running | Client B is unaffected; env was cloned at session creation |
| Client B attaches a session Client A created while that session is still live | Refused unless both clients forward exactly the same environment; per-terminal variables such as TERM_SESSION_ID usually differ, so start a new session instead |
Client A reconnects with the same tui_id | Old entry is removed, new entry with fresh env is registered; already-running sessions keep their original clone |
The last point matters: the registry hands the session a clone, not a reference. Once a session is created it owns its env snapshot. Reconnects or disconnects of the originating client have no effect on running sessions.
Risk profile passthrough (explicit allowlist)
shell_env_passthrough on a risk profile controls which variables from the
daemon’s own process environment are passed to shell subprocesses. This is
useful when you want specific vars available regardless of whether zerocode is
connected, for example on a headless server where the daemon itself has the
vars set.
Subagents cannot expand this list beyond what the parent policy allows: adding a var not present on the parent’s list is rejected as a policy escalation.