diff --git a/.claude/rules/cli.md b/.claude/rules/cli.md index d49b9222..a954b6bd 100644 --- a/.claude/rules/cli.md +++ b/.claude/rules/cli.md @@ -30,7 +30,7 @@ bun run cli version ## Startup Behavior 1. `@archon/paths/strip-cwd-env-boot` (first import) removes all Bun-auto-loaded CWD `.env` keys from `process.env` -2. Loads `~/.archon/.env` (no `override` needed — CWD keys are already stripped) +2. Loads `~/.archon/.env` with `override: true` (Archon config wins over shell-inherited vars) 3. Smart Claude auth default: if no `CLAUDE_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`, sets `CLAUDE_USE_GLOBAL_AUTH=true` 4. Imports all commands AFTER dotenv setup diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 058e4279..d7dedf48 100755 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -30,16 +30,8 @@ if (existsSync(globalEnvPath)) { } } -// Warn when running inside a Claude Code session — nested sessions can deadlock. -if (process.env.CLAUDECODE === '1' && !process.env.ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING) { - process.stderr.write( - '\u26a0 Detected CLAUDECODE=1 — you appear to be running `archon` from inside a Claude Code session.\n' + - ' If workflows hang silently at dag_node_started, this is a known class of issue.\n' + - ' Workaround: run `archon serve` from a regular shell and use the web UI or HTTP API.\n' + - ' Suppress: set ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING=1\n' + - ' Details: https://github.com/coleam00/Archon/issues/1067\n' - ); -} +// CLAUDECODE=1 warning is emitted inside stripCwdEnv() (boot import above) +// BEFORE the marker is deleted from process.env. No duplicate warning here. // Smart defaults for Claude auth // If no explicit tokens, default to global auth from `claude /login` diff --git a/packages/core/src/clients/claude.ts b/packages/core/src/clients/claude.ts index e24bca80..90595e1d 100644 --- a/packages/core/src/clients/claude.ts +++ b/packages/core/src/clients/claude.ts @@ -82,66 +82,30 @@ function normalizeClaudeUsage(usage?: { } /** - * Build environment for Claude subprocess + * Build environment for Claude subprocess. * - * Auth behavior: - * - CLAUDE_USE_GLOBAL_AUTH=true: Filter tokens, use global auth from `claude /login` - * - CLAUDE_USE_GLOBAL_AUTH=false: Pass tokens through explicitly - * - Not set: Auto-detect — use explicit tokens if present, otherwise fall back to global auth + * process.env is already clean at this point: + * - stripCwdEnv() at entry point removed CWD .env keys + CLAUDECODE markers + * - ~/.archon/.env loaded with override:true as the trusted source + * + * Auth mode is determined by the SDK based on what tokens are present: + * - Tokens in env → SDK uses them (explicit auth) + * - No tokens → SDK uses `claude /login` credentials (global auth) + * - User controls this by what they put in ~/.archon/.env + * + * We log the detected mode for diagnostics but don't filter — the user's + * config is trusted. See coleam00/Archon#1067 for design rationale. */ function buildSubprocessEnv(): NodeJS.ProcessEnv { - const globalAuthSetting = process.env.CLAUDE_USE_GLOBAL_AUTH?.toLowerCase(); - - // Check for empty token values (common misconfiguration) - const tokenVars = ['CLAUDE_CODE_OAUTH_TOKEN', 'CLAUDE_API_KEY'] as const; - const emptyTokens = tokenVars.filter(v => process.env[v] === ''); - if (emptyTokens.length > 0) { - getLog().warn({ emptyTokens }, 'empty_token_values'); - } - - // Warn if user has the legacy variable but not the new ones - if ( - process.env.ANTHROPIC_API_KEY && - !process.env.CLAUDE_CODE_OAUTH_TOKEN && - !process.env.CLAUDE_API_KEY - ) { - getLog().warn( - { hint: 'Use CLAUDE_API_KEY or CLAUDE_CODE_OAUTH_TOKEN instead' }, - 'deprecated_anthropic_api_key_ignored' - ); - } - const hasExplicitTokens = Boolean( process.env.CLAUDE_CODE_OAUTH_TOKEN ?? process.env.CLAUDE_API_KEY ); + const authMode = hasExplicitTokens ? 'explicit' : 'global'; + getLog().info( + { authMode }, + authMode === 'global' ? 'using_global_auth' : 'using_explicit_tokens' + ); - // Determine whether to use global auth - let useGlobalAuth: boolean; - if (globalAuthSetting === 'true') { - useGlobalAuth = true; - getLog().info({ authMode: 'global' }, 'using_global_auth'); - } else if (globalAuthSetting === 'false') { - useGlobalAuth = false; - getLog().info({ authMode: 'explicit' }, 'using_explicit_tokens'); - } else if (globalAuthSetting !== undefined) { - // Unrecognized value - warn and fall back to auto-detect - getLog().warn({ value: globalAuthSetting }, 'unrecognized_global_auth_setting'); - useGlobalAuth = !hasExplicitTokens; - } else { - // Not set - auto-detect: use tokens if present, otherwise global auth - useGlobalAuth = !hasExplicitTokens; - if (hasExplicitTokens) { - getLog().info({ authMode: 'explicit', autoDetected: true }, 'using_explicit_tokens'); - } else { - getLog().info({ authMode: 'global', autoDetected: true }, 'using_global_auth'); - } - } - - // process.env is already clean — stripCwdEnv() at entry point removed CWD - // .env keys and CLAUDECODE markers. Everything remaining is user-trusted - // (shell env + ~/.archon/.env). Pass it through as-is. - const envKeyCount = Object.keys(process.env).length; - getLog().debug({ envKeyCount, useGlobalAuth, hasExplicitTokens }, 'subprocess_env_prepared'); return { ...process.env }; } diff --git a/packages/docs-web/src/content/docs/reference/configuration.md b/packages/docs-web/src/content/docs/reference/configuration.md index 9d5a66e3..c126b968 100644 --- a/packages/docs-web/src/content/docs/reference/configuration.md +++ b/packages/docs-web/src/content/docs/reference/configuration.md @@ -298,7 +298,7 @@ Infrastructure configuration (database URL, platform tokens) is stored in `.env` | Component | Location | Purpose | |-----------|----------|---------| -| **CLI** | `~/.archon/.env` | Global infrastructure config; CWD .env keys stripped before loading (no `override` needed) | +| **CLI** | `~/.archon/.env` | Global infrastructure config; CWD .env keys stripped first, then loaded with `override: true` (Archon config wins over shell-inherited vars) | | **Server (dev)** | `/.env` + `~/.archon/.env` | Repo `.env` for platform tokens; `~/.archon/.env` loaded with `override: true` | | **Server (binary)** | `~/.archon/.env` | Single source of truth (repo `.env` path is not available in compiled binaries) | diff --git a/packages/paths/src/strip-cwd-env.ts b/packages/paths/src/strip-cwd-env.ts index 8a7dce7d..17c4a3c9 100644 --- a/packages/paths/src/strip-cwd-env.ts +++ b/packages/paths/src/strip-cwd-env.ts @@ -68,6 +68,16 @@ export function stripCwdEnv(cwd: string = process.cwd()): void { // --- Pass 2: Nested Claude Code session markers --- // Pattern-matched (not hardcoded) so new CLAUDE_CODE_* markers added by // future Claude Code versions are automatically handled. + // Emit warning BEFORE deleting — downstream code won't see CLAUDECODE=1. + if (process.env.CLAUDECODE === '1' && !process.env.ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING) { + process.stderr.write( + '\u26a0 Detected CLAUDECODE=1 \u2014 running inside a Claude Code session.\n' + + ' If workflows hang silently, this is a known class of issue.\n' + + ' Workaround: run `archon serve` from a regular shell.\n' + + ' Suppress: set ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING=1\n' + + ' Details: https://github.com/coleam00/Archon/issues/1067\n' + ); + } if (process.env.CLAUDECODE) { Reflect.deleteProperty(process.env, 'CLAUDECODE'); } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d4ab210d..0b502008 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -40,16 +40,8 @@ if (existsSync(globalEnvPath)) { } } -// Warn when running inside a Claude Code session — nested sessions can deadlock. -if (process.env.CLAUDECODE === '1' && !process.env.ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING) { - process.stderr.write( - '\u26a0 Detected CLAUDECODE=1 — server started from inside a Claude Code session.\n' + - ' If AI queries hang silently, this is a known class of issue.\n' + - ' Workaround: start the server from a regular (non-Claude-Code) shell.\n' + - ' Suppress: set ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING=1\n' + - ' Details: https://github.com/coleam00/Archon/issues/1067\n' - ); -} +// CLAUDECODE=1 warning is emitted inside stripCwdEnv() (boot import above) +// BEFORE the marker is deleted from process.env. No duplicate warning here. // Smart default: use Claude Code's built-in OAuth if no explicit credentials if (