diff --git a/internal/gateway/claude_warmup.go b/internal/gateway/claude_warmup.go index e8143c6c..88dc558a 100644 --- a/internal/gateway/claude_warmup.go +++ b/internal/gateway/claude_warmup.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "os" + "slices" "strings" "github.com/yetone/magpie/internal/netproxy" @@ -90,7 +91,17 @@ func claudeUsage(ctx context.Context) (string, error) { cmd := proc.CommandContext(ctx, binary, claudeUsageArgs()...) cmd.Dir = tmp cmd.Stdin = strings.NewReader("") - cmd.Env = netproxy.EnvWith(claudeProxy(ctx), cleanClaudeEnv(os.Environ())) + // CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC makes /usage skip its request + // and print only a reading another run left in the last hour, so it stays + // only when the user set it. + env := cleanClaudeEnv(os.Environ()) + if os.Getenv("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC") == "" { + env = slices.DeleteFunc(env, func(e string) bool { + return strings.HasPrefix(e, "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=") + }) + } + env = append(env, "DISABLE_TELEMETRY=1", "DISABLE_ERROR_REPORTING=1", "DISABLE_AUTOUPDATER=1") + cmd.Env = netproxy.EnvWith(claudeProxy(ctx), env) var out, stderr bytes.Buffer cmd.Stdout, cmd.Stderr = &out, &stderr runErr := cmd.Run() diff --git a/internal/gateway/claude_warmup_test.go b/internal/gateway/claude_warmup_test.go index 1bada7e6..bf49614c 100644 --- a/internal/gateway/claude_warmup_test.go +++ b/internal/gateway/claude_warmup_test.go @@ -23,7 +23,9 @@ func fakeWarmClaude(t *testing.T, out string, code int) string { log := filepath.Join(dir, "log") script := "#!/bin/sh\n" + "{ printf 'args:'; for a in \"$@\"; do printf '[%s]' \"$a\"; done; echo; " + - "echo \"dir:$CLAUDE_CONFIG_DIR\"; echo \"base:$ANTHROPIC_BASE_URL\"; echo \"pwd:$(pwd)\"; printf 'stdin:'; cat; echo; } > " + log + "\n" + + "echo \"dir:$CLAUDE_CONFIG_DIR\"; echo \"base:$ANTHROPIC_BASE_URL\"; echo \"pwd:$(pwd)\"; printf 'stdin:'; cat; echo; " + + "echo \"traffic:$CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\"; echo \"telemetry:$DISABLE_TELEMETRY\"; " + + "echo \"errors:$DISABLE_ERROR_REPORTING\"; echo \"updater:$DISABLE_AUTOUPDATER\"; } > " + log + "\n" + "echo '" + out + "'\nexit " + string(rune('0'+code)) + "\n" if err := os.WriteFile(filepath.Join(dir, "claude"), []byte(script), 0o755); err != nil { t.Fatal(err) @@ -123,6 +125,38 @@ func TestClaudeUsageRunsClaudeCode(t *testing.T) { } } +// /usage runs without CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, which keeps +// Claude Code from asking for the usage, unless the user set it; telemetry, +// error reporting and autoupdate stay off. The warm-up keeps it. +func TestClaudeUsageSendsItsRequest(t *testing.T) { + log := fakeWarmClaude(t, `{"type":"result","is_error":false,"result":"Current session: 13% used"}`, 0) + for _, k := range []string{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "DISABLE_TELEMETRY", "DISABLE_ERROR_REPORTING", "DISABLE_AUTOUPDATER"} { + t.Setenv(k, "") + } + if _, err := claudeUsage(context.Background()); err != nil { + t.Fatal(err) + } + b, _ := os.ReadFile(log) + for _, want := range []string{"traffic:\n", "telemetry:1\n", "errors:1\n", "updater:1\n"} { + if !strings.Contains(string(b), want) { + t.Errorf("/usage run lacks %q:\n%s", want, b) + } + } + if err := warmClaude(context.Background(), ""); err != nil { + t.Fatal(err) + } + if b, _ := os.ReadFile(log); !strings.Contains(string(b), "traffic:1\n") { + t.Errorf("warm-up lacks CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:\n%s", b) + } + t.Setenv("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1") + if _, err := claudeUsage(context.Background()); err != nil { + t.Fatal(err) + } + if b, _ := os.ReadFile(log); !strings.Contains(string(b), "traffic:1\n") { + t.Errorf("/usage run dropped the user's CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:\n%s", b) + } +} + // With Claude Code's verbose output on (its /config, kept in .claude.json), // claude -p --output-format json prints every message as an array, the // system init first and the result last (KevinXC on Discord: usage