From fe0012635ae53bcc55d43c0a97012e371ad391f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 14 Aug 2026 15:36:32 +0000 Subject: [PATCH] stop leaking the parent claude session into the one we spawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pm2 inherits the environment of whoever ran pm2 start, so restarting this sidecar from inside a claude code terminal — which is how it is restarted most of the time — bakes that terminal's session into the daemon. right now this process is carrying CLAUDE_CODE_MESSAGING_SOCKET for an unrelated pid that has been alive for an hour and a half. three of these were already stripped; the rest arrived with 2.x and were never added. this is hygiene, not the fix for today's hang — a spawn was verified to succeed with the whole set present — but a child attaching to a stranger's ipc socket is not a failure anyone would recognise from the symptom. Co-Authored-By: Claude Opus 5 --- src/servers/sidecar/claude/claude-manager.ts | 35 +++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/servers/sidecar/claude/claude-manager.ts b/src/servers/sidecar/claude/claude-manager.ts index eb721668..d9cee15c 100644 --- a/src/servers/sidecar/claude/claude-manager.ts +++ b/src/servers/sidecar/claude/claude-manager.ts @@ -34,6 +34,39 @@ function resolveClaudeBin(): string { const CLAUDE_BIN = resolveClaudeBin(); console.log(`[claude] CLI resolved to ${CLAUDE_BIN}`); +/** + * Variables the `claude` CLI injects to describe ITS OWN session, which must never reach a `claude` we + * spawn ourselves. + * + * They arrive here by an ordinary accident: PM2 inherits the environment of whoever ran `pm2 start`, so + * restarting this sidecar from inside a Claude Code terminal — which is how it is restarted most of the + * time — bakes that terminal's session into the daemon. On 2026-08-14 this process was carrying + * `CLAUDE_CODE_MESSAGING_SOCKET` for an unrelated PID that had been alive for an hour and a half. + * + * Only three of these were stripped before (`CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, `CLAUDE_CODE_SSE_PORT`); + * the rest are newer and arrived with 2.x. Stripping them is hygiene rather than a fix — a spawn was + * verified to succeed with the whole set present — but the failure it prevents is a child attaching to a + * stranger's IPC socket, which would be extremely hard to recognise from the symptom. + */ +const NESTED_SESSION_ENV = [ + 'CLAUDECODE', + 'CLAUDE_CODE_ENTRYPOINT', + 'CLAUDE_CODE_SSE_PORT', + 'CLAUDE_CODE_CHILD_SESSION', + 'CLAUDE_CODE_MESSAGING_SOCKET', + 'CLAUDE_CODE_MESSAGING_TOKEN', + 'CLAUDE_CODE_SESSION_ID', + 'CLAUDE_CODE_EXECPATH', + 'CLAUDE_PID', +] as const; + +/** `process.env` minus the parent session's fingerprint. */ +function envWithoutParentSession(): Record { + const out: Record = { ...process.env } as Record; + for (const name of NESTED_SESSION_ENV) delete out[name]; + return out; +} + // Capture original HOME before user-instance overrides it const HOST_HOME = process.env.HOME!; import { DATA_PATH } from '../../data-path'; @@ -404,7 +437,7 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat }; // Strip the nested-session guard vars so the SDK can spawn `claude` (mirrors the old spawn env clean). - const { CLAUDECODE: _c, CLAUDE_CODE_ENTRYPOINT: _e, CLAUDE_CODE_SSE_PORT: _s, ...cleanEnv } = process.env; + const cleanEnv = envWithoutParentSession(); const resumeId = getClaudeSession(sessionKey, params.userId) ?? params.resumeSessionId; const subModel = params.model?.split('/')[1];