fix: strip all Claude Code session env vars from child processes - #430
Open
alekb wants to merge 1 commit into
Open
fix: strip all Claude Code session env vars from child processes#430alekb wants to merge 1 commit into
alekb wants to merge 1 commit into
Conversation
Stripping CLAUDECODE alone stopped being enough once Claude Code 2.1.x began marking spawned processes with a family of per-session vars. Any one of them left in the child env makes the spawned claude behave as a nested session: it attaches to the parent's session plumbing and never writes its own transcript, so a PTY wrapper like fya waits for output that never arrives and each turn burns the full --turn-timeout (30m by default) having done no work and left no diagnostic beyond the timeout itself. Observed running ralphex from inside a Claude Code session on Claude Code 2.1.228: /proc/<pid>/environ on the fya child showed CLAUDECODE correctly removed but CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_MESSAGING_SOCKET, CLAUDE_CODE_BRIDGE_SESSION_ID and friends still set. Iterations timed out at 30m each with no transcript written; relaunching with the full set scrubbed produced a transcript in ~85s and normal progress. Collect the markers in sessionEnvVars and strip them in both executors - claudeChildEnv and the codex runner's childEnv have the same defect, and the codex loop is reached in the same run. The list is explicit rather than a CLAUDE_CODE_* prefix strip because user-set config vars share that prefix (CLAUDE_CODE_USE_BEDROCK, _USE_VERTEX, _MAX_OUTPUT_TOKENS, _SUBAGENT_MODEL) and must still reach the child; a test covers that. The set is "every session-scoped marker" rather than a bisected minimal one - which var trips the nested-session detection is Claude Code's business, and a wrapper is better off dropping all of them, including any added later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhHLFUDbCTxtsg4zis4aP4
Owner
|
lgtm on |
umputun
added a commit
that referenced
this pull request
Aug 13, 2026
execCustomRunner.Run never assigns cmd.Env, so a custom external review script inherits every Claude Code session marker. The two sibling runners in pkg/executor both filter. Surfaced reviewing PR #430; predates it, so left out of that change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Stripping
CLAUDECODEalone stopped being enough once Claude Code 2.1.x began marking spawned processes with a family of per-session vars. Any one of them left in the child env makes the spawnedclaudebehave as a nested session: it attaches to the parent's session plumbing and never writes its own transcript. A PTY wrapper such as fya then waits for output that never arrives, and every turn burns the full--turn-timeout(30m by default) having done no work.The failure is near-silent — the only symptom is
FYA_TRANSIENT_TIMEOUT, which ralphex correctly classifies as transient and retries, so the run loops at 30 minutes per iteration making no progress.Evidence
Running ralphex from inside a Claude Code session, Claude Code 2.1.228, fya 0.4.0, ralphex v1.6.1.
/proc/<pid>/environon the fya child:Two iterations timed out at 30m each, with no session transcript written under
~/.claude/projects/<cwd-slug>/for the entire run. Relaunching the same plan with the full set scrubbed (env -u … ralphex …) produced a child transcript in ~85 seconds and normal streaming progress.Change
Collect the markers in
sessionEnvVarsand strip them in both executors.claudeChildEnv(executor.go) and the codex runner'schildEnv(codex.go) have the same defect, and in a full-mode run the codex loop is reached by the same invocation, so both are fixed here rather than split.Deliberately an explicit list rather than a
CLAUDE_CODE_*prefix strip: user-set config vars share that prefix (CLAUDE_CODE_USE_BEDROCK,_USE_VERTEX,_MAX_OUTPUT_TOKENS,_SUBAGENT_MODEL) and must still reach the child. A test covers that so the distinction doesn't get "simplified" away later.Tests
Cases added to the existing tables in
TestClaudeChildEnvandTestExecCodexRunner_childEnv:preserveAPIKey/stripAnthropicKeymodesCLAUDE_CODE_*config vars andCLAUDE_CONFIG_DIRsurviveVar names are spelled out literally in the tests rather than looped over
sessionEnvVars, so removing an entry from the production list fails the test instead of agreeing with itself.make fmtclean,make lint0 issues,make testpasses (race, full suite, 87.9% total coverage).One caveat worth stating
The set is "every session-scoped marker", not a bisected minimal one. I verified that all-stripped works and all-present reproduces the wedge; I did not test each var individually, since the nested-session detection lives in Claude Code rather than here.
CLAUDE_PIDandCLAUDE_EFFORTare the two most likely to be unnecessary — happy to drop them if you'd rather keep the list tight. My reasoning for the wider net is that a wrapper is better off dropping anything session-scoped, including markers added in future releases.AI-assisted, reviewed by me before submitting; I can explain any line.