From f2d8604ad1f0dafbb375aa2f69d8633a28dcae0b Mon Sep 17 00:00:00 2001 From: pdfinn Date: Sat, 21 Feb 2026 22:09:48 +0800 Subject: [PATCH] fix(llm): strip CLAUDECODE env var before spawning claude subprocess The claude CLI refuses to run when CLAUDECODE is set in the environment, as it detects a nested Claude Code session. When llm9p is launched from within Claude Code, all subprocesses inherit this variable and every LLM call fails with "Cannot be launched inside another Claude Code session". Added claudeEnv() helper that filters CLAUDECODE from os.Environ() before passing the environment to cmd. Replaced all five cmd.Environ() call sites in cli_client.go (Ask, Compact, StartStream, AskWithHistory, AskWithRequest). Co-Authored-By: Claude Sonnet 4.6 --- internal/llm/cli_client.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/internal/llm/cli_client.go b/internal/llm/cli_client.go index 25031ced45139d7faf618d3ce3fb0c341c8b6bb6..1139f43fac562a11645222e5f7033d80eb0532e2 100644 --- a/internal/llm/cli_client.go +++ b/internal/llm/cli_client.go @@ -7,11 +7,25 @@ import ( "context" "encoding/json" "fmt" + "os" "os/exec" "strings" "sync" ) +// claudeEnv returns the current process environment with CLAUDECODE stripped. +// The claude CLI refuses to run inside another Claude Code session (CLAUDECODE is set). +// llm9p is a middleware server, not a Claude Code session, so this is safe to remove. +func claudeEnv() []string { + env := make([]string, 0, len(os.Environ())) + for _, e := range os.Environ() { + if !strings.HasPrefix(e, "CLAUDECODE=") { + env = append(env, e) + } + } + return env +} + // CLIClient uses the Claude Code CLI for LLM requests. // This allows using a Claude Max subscription instead of API tokens. type CLIClient struct { @@ -227,7 +241,7 @@ func (c *CLIClient) Compact(ctx context.Context) error { cmd.Stdin = bytes.NewBufferString(summaryPrompt) // Set thinking token budget - cmd.Env = append(cmd.Environ(), func() string { + cmd.Env = append(claudeEnv(), func() string { if thinkingTokens < 0 { return "MAX_THINKING_TOKENS=31999" } @@ -328,7 +342,7 @@ func (c *CLIClient) Ask(ctx context.Context, prompt string) (string, error) { // Set thinking token budget via environment variable // -1 = max (31999), 0 = disabled, >0 = specific budget - cmd.Env = append(cmd.Environ(), func() string { + cmd.Env = append(claudeEnv(), func() string { if thinkingTokens < 0 { return "MAX_THINKING_TOKENS=31999" } @@ -458,7 +472,7 @@ func (c *CLIClient) StartStream(ctx context.Context, prompt string) error { cmd.Stdin = bytes.NewBufferString(fullPrompt) // Set thinking token budget via environment variable - cmd.Env = append(cmd.Environ(), func() string { + cmd.Env = append(claudeEnv(), func() string { if thinkingTokens < 0 { return "MAX_THINKING_TOKENS=31999" } @@ -622,7 +636,7 @@ func (c *CLIClient) AskWithHistory(ctx context.Context, history []Message, promp cmd.Stdin = bytes.NewBufferString(fullPrompt) // Set thinking token budget via environment variable - cmd.Env = append(cmd.Environ(), func() string { + cmd.Env = append(claudeEnv(), func() string { if thinkingTokens < 0 { return "MAX_THINKING_TOKENS=31999" } @@ -717,7 +731,7 @@ func (c *CLIClient) AskWithRequest(ctx context.Context, req AskRequest) (string, cmd.Stdin = bytes.NewBufferString(fullPrompt) // Set thinking token budget via environment variable - cmd.Env = append(cmd.Environ(), func() string { + cmd.Env = append(claudeEnv(), func() string { if thinkingTokens < 0 { return "MAX_THINKING_TOKENS=31999" }