From 33a94d2bc4b175addd4d2903747762402c837674 Mon Sep 17 00:00:00 2001 From: cnzgray Date: Fri, 27 Feb 2026 22:15:19 +0800 Subject: [PATCH] fix(executor): isolate CLAUDE_CODE_TMPDIR for nested claude to fix (no output) (#154) * fix(executor): isolate CLAUDE_CODE_TMPDIR for nested claude to fix (no output) Claude 2.1.45+ calls Nz7() in preAction to clean its tasks directory on startup. When claude runs as a nested subprocess, it deletes the parent session's *.output files, causing the parent to read an empty string and display "(no output)". Fix: assign each nested claude process its own unique CLAUDE_CODE_TMPDIR (os.TempDir()/cc-nested--) so it only cleans its own tasks directory and never touches the parent's output files. * fix(executor): use MkdirTemp for nested tmpdir --------- Co-authored-by: cexll --- .../internal/executor/env_stderr_test.go | 6 ++++++ .../internal/executor/executor.go | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/codeagent-wrapper/internal/executor/env_stderr_test.go b/codeagent-wrapper/internal/executor/env_stderr_test.go index 2af7439..cfd2504 100644 --- a/codeagent-wrapper/internal/executor/env_stderr_test.go +++ b/codeagent-wrapper/internal/executor/env_stderr_test.go @@ -125,6 +125,9 @@ func TestEnvInjection_LogsToStderrAndMasksKey(t *testing.T) { if cmd.env["ANTHROPIC_API_KEY"] != apiKey { t.Fatalf("ANTHROPIC_API_KEY=%q, want %q", cmd.env["ANTHROPIC_API_KEY"], apiKey) } + if cmd.env["CLAUDE_CODE_TMPDIR"] == "" { + t.Fatalf("expected CLAUDE_CODE_TMPDIR to be set for nested claude, got empty") + } if !strings.Contains(got, "Env: ANTHROPIC_BASE_URL="+baseURL) { t.Fatalf("stderr missing base URL env log; stderr=%q", got) @@ -132,4 +135,7 @@ func TestEnvInjection_LogsToStderrAndMasksKey(t *testing.T) { if !strings.Contains(got, "Env: ANTHROPIC_API_KEY=eyJh****test") { t.Fatalf("stderr missing masked API key log; stderr=%q", got) } + if !strings.Contains(got, "CLAUDE_CODE_TMPDIR: ") { + t.Fatalf("stderr missing CLAUDE_CODE_TMPDIR log; stderr=%q", got) + } } diff --git a/codeagent-wrapper/internal/executor/executor.go b/codeagent-wrapper/internal/executor/executor.go index 1f95301..d1e79aa 100644 --- a/codeagent-wrapper/internal/executor/executor.go +++ b/codeagent-wrapper/internal/executor/executor.go @@ -1154,10 +1154,23 @@ func RunCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe injectTempEnv(cmd) - // Claude Code sets CLAUDECODE=1 in its child processes. If we don't - // remove it, the spawned `claude -p` detects the variable and refuses - // to start ("cannot be launched inside another Claude Code session"). if commandName == "claude" { + // Claude 2.1.45+ calls Nz7() on startup to clean its tasks directory, + // which deletes the parent session's *.output files and causes "(no output)". + // Assign each nested claude its own isolated tmpdir so it only cleans its own files. + nestedTmpDir, err := os.MkdirTemp("", fmt.Sprintf("cc-nested-%d-", os.Getpid())) + if err != nil { + logWarnFn("Failed to create isolated CLAUDE_CODE_TMPDIR: " + err.Error()) + } else { + cmd.SetEnv(map[string]string{"CLAUDE_CODE_TMPDIR": nestedTmpDir}) + defer os.RemoveAll(nestedTmpDir) //nolint:errcheck + logInfoFn("CLAUDE_CODE_TMPDIR: " + nestedTmpDir) + fmt.Fprintln(os.Stderr, " CLAUDE_CODE_TMPDIR: "+nestedTmpDir) + } + + // Claude Code sets CLAUDECODE=1 in its child processes. If we don't + // remove it, the spawned `claude -p` detects the variable and refuses + // to start ("cannot be launched inside another Claude Code session"). cmd.UnsetEnv("CLAUDECODE") }