fix: move CLAUDECODE warning into stripCwdEnv, remove dead useGlobalAuth logic

Review findings addressed:

1. CLAUDECODE warning was dead code — the boot import deleted CLAUDECODE
   from process.env before the warning check in cli.ts/server/index.ts
   could fire. Moved the warning into stripCwdEnv() itself, emitted
   BEFORE the deletion. Removed duplicate warning code from both entry
   points.

2. useGlobalAuth token stripping removed (intentional, not regression) —
   the old code stripped CLAUDE_CODE_OAUTH_TOKEN and CLAUDE_API_KEY when
   useGlobalAuth=true. Per design discussion: the user controls
   ~/.archon/.env and all keys they set are intentional. If they want
   global auth, they just don't set tokens. Simplified buildSubprocessEnv
   to log auth mode for diagnostics only, no filtering.

3. Docs "no override needed" corrected — cli.md and configuration.md
   now reflect the actual code (override: true).
This commit is contained in:
Rasmus Widing 2026-04-12 12:04:57 +03:00
parent c1eb071308
commit 76b49b5b3a
6 changed files with 33 additions and 75 deletions

View file

@ -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

View file

@ -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`

View file

@ -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 };
}

View file

@ -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)** | `<archon-repo>/.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) |

View file

@ -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');
}

View file

@ -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 (