Settings env removals never propagate to daemon-hosted sessions (Agents View/background): stale inherited env silently disables cross-session messaging, and no settings value can neutralize it (v2.1.224)

Status Open
Maintainer reply None cached
Activity 2 comments · opened Aug 8, 2026

Summary

Removing a variable from the settings.json env map never takes effect for sessions hosted by the background daemon (Agents View chats, background jobs): env injection is additive over inherited process env, and daemon-spawned session hosts inherit the daemon's env snapshot from whenever the daemon started — in my case 28+ hours earlier. Because the four cross-session-messaging gate variables are documented as "any non-empty value, including 0/false, still disables; unset to re-enable" (CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, DISABLE_TELEMETRY), there is no settings-level value that can neutralize the stale inherited var — the documented remediation in cross-session messaging → Availability ("Unset whichever applies") is unachievable for daemon-hosted sessions. The observable result is availability silently diverging on one machine: plain claude sessions get /list-agents, a Peer address row, and a bound inbox socket, while every Agents View chat started the same minute from the same terminal doesn't — with no error, no diagnostic, and nothing pointing at the daemon.

Environment

  • Claude Code v2.1.224, npm global install under mise node 24.12.0 (/home/will/.local/share/mise/installs/node/24.12.0/lib/node_modules/@anthropic-ai/claude-code)
  • WSL2 Ubuntu, kernel 6.6.114.1-microsoft-standard-WSL2
  • Daemon: transient, started on demand by claude agents (per claude daemon status), uptime 127,569 s (~35 h) at diagnosis, held open by 11 bg workers

Repro steps

  1. Have "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" (any marker var works, but this one shows the messaging impact) in ~/.claude/settings.jsonenv.
  2. Run claude agents once so the transient daemon spawns (claude daemon statusorigin: transient). Leave a background session running so the daemon stays alive.
  3. Remove the variable from settings.json env.
  4. Open a new terminal (shell env clean; nothing else sets the var):
  • claude -p 'run printenv CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and printenv CLAUDE_CODE_MESSAGING_SOCKET' → var unset, inbox socket bound (/run/user/1001/cc-socks/<pid>.sock), ListAgents tool present. Also true with --dangerously-skip-permissions.
  1. From that same fresh terminal, claude agents → start a new chat → attach:
  • the session host is a pre-warmed claude bg-spare/claude bg-pty-host forked by the day-old daemon; its /proc/<pid>/environ still contains the removed var; no inbox socket binds; /list-agents is not recognized; /status shows no Peer address row.
  1. claude daemon stop, relaunch claude agents → new daemon inherits the clean env → new chats bind sockets and /list-agents works. (Step 6 is the workaround; the daemon-respawn-on-demand behavior is per claude daemon --help.)

Evidence collected

/proc/<pid>/environ scan of every live claude process while reproducing (flag = removed var still present):

pid=934405   flag=False  claude agents --dangerously-skip-permissions        (fresh front-end, clean)
pid=1560142  flag=False  claude agents --dangerously-skip-permissions        (fresh front-end, clean)
pid=1560210  flag=True   claude bg-pty-host ... /tmp/cc-daemon-1001/e6fb62ab/spare/4486810d.pty.sock
pid=1560299  flag=True   claude bg-spare    ... spare/4486810d.claim.sock    (forked 9h AFTER the settings change)
pid=1590636  flag=True   claude bg-pty-host ... spare/c804d0c5.pty.sock      (forked 11h AFTER the settings change)
... (all 11 bg workers: flag=True)
pid=2071812  flag=True   @anthropic-ai/claude-code/bin/claude daemon run ... (started Aug 7 05:07, 28h before the change)

PPID chain: every bg-spare/bg-pty-host parents directly to daemon pid 2071812. A headless probe session asked to call ListAgents returned "No reachable agents" — correct, since no other session on the machine could bind a socket.

Why this is worse than ordinary daemon-env caching

  1. Additive injection: settings env injection adds/overrides vars but never unsets one that settings no longer defines, so the inherited stale var survives every new session the daemon hosts, indefinitely.
  2. No override value exists: for CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and DISABLE_TELEMETRY, any non-empty value disables (docs: "Setting it to 0 or false still disables"), so a user cannot even write a neutralizing value into settings — unset-at-source is the only documented path, and it doesn't reach these sessions.
  3. No diagnostic: the Availability checklist says work through version/OS/provider/env-vars — all of which check out in the user's fresh terminal. Nothing surfaces "this session's env came from a 35-hour-old daemon". /status, claude doctor, and claude daemon status are all silent about the divergence.

Suggested fixes (ranked)

  1. When claiming a spare / spawning a session host, re-derive the settings env map from a fresh settings.json read, and drop from the child env any var that an earlier settings injection added but current settings no longer defines (requires remembering which vars came from settings vs the user's shell — a small manifest in the daemon would do).
  2. Daemon watches settings.json env (hash check on spawn is enough): on drift, recycle pre-warmed spares and log it; claude daemon status shows env: stale since <time> until then.
  3. Cheapest: docs + surfacing. Add to cross-session-messaging Availability and env-vars: "sessions hosted by the background daemon inherit its environment; after changing these variables run claude daemon stop". Have /doctor (or the /list-agents unrecognized-state hint) warn when the session env contains a gate var that current settings.json does not define.

Related

  • #58383 (closed, fixed v2.1.140): DISABLE_TELEMETRY silently gating Agent View via feature-flag coupling — same family of silent env-var gating; the maintainer noted broader killswitch coupling remained open.
  • #84945 (open): inbox socket silently fails to bind with no surfaced error — different mechanism, same undiagnosable end state; a /status-level socket/health indicator would help both.
  • #52787 (closed): decoupling /feedback from the nonessential-traffic var — prior art that these kill-switches over-reach.

Workaround for anyone hitting this

claude daemon stop (daemon respawns on demand with your current env), then relaunch claude agents. Find a suspect daemon with claude daemon status; confirm with tr '\0' '\n' < /proc/<daemon-pid>/environ | grep <var>.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗