fix(delegation): strip claudecode in core delegation spawn paths

- add shared CLAUDECODE sanitizer for child env construction

- apply sanitizer in execClaude and headless delegation execution

- sanitize GLMT embedded Claude spawn environment

Refs #588
This commit is contained in:
Tam Nhu Tran
2026-02-20 23:00:33 +07:00
parent 4303ee48c9
commit 50412dc679
3 changed files with 23 additions and 6 deletions
+3 -3
View File
@@ -37,7 +37,7 @@ import { handleShellCompletionCommand } from './commands/shell-completion-comman
import { handleUpdateCommand } from './commands/update-command'; import { handleUpdateCommand } from './commands/update-command';
// Import extracted utility functions // Import extracted utility functions
import { execClaude, escapeShellArg } from './utils/shell-executor'; import { execClaude, escapeShellArg, stripClaudeCodeEnv } from './utils/shell-executor';
import { wireChildProcessSignals } from './utils/signal-forwarder'; import { wireChildProcessSignals } from './utils/signal-forwarder';
// Import target adapter system // Import target adapter system
@@ -197,13 +197,13 @@ async function execClaudeWithProxy(
const needsShell = isWindows && /\.(cmd|bat)$/i.test(claudeCli); const needsShell = isWindows && /\.(cmd|bat)$/i.test(claudeCli);
const webSearchEnv = getWebSearchHookEnv(); const webSearchEnv = getWebSearchHookEnv();
const imageAnalysisEnv = getImageAnalysisHookEnv(profileName); const imageAnalysisEnv = getImageAnalysisHookEnv(profileName);
const env = { const env = stripClaudeCodeEnv({
...process.env, ...process.env,
...envVars, ...envVars,
...webSearchEnv, ...webSearchEnv,
...imageAnalysisEnv, ...imageAnalysisEnv,
CCS_PROFILE_TYPE: 'settings', // Signal to WebSearch hook this is a third-party provider CCS_PROFILE_TYPE: 'settings', // Signal to WebSearch hook this is a third-party provider
}; });
let claude: ChildProcess; let claude: ChildProcess;
if (isPowerShellScript) { if (isPowerShellScript) {
+2 -1
View File
@@ -17,6 +17,7 @@ import { StreamBuffer, formatToolVerbose } from './executor/stream-parser';
import { buildExecutionResult } from './executor/result-aggregator'; import { buildExecutionResult } from './executor/result-aggregator';
import { getCcsDir, getModelDisplayName } from '../utils/config-manager'; import { getCcsDir, getModelDisplayName } from '../utils/config-manager';
import { getProfileLookupCandidates } from '../utils/profile-compat'; import { getProfileLookupCandidates } from '../utils/profile-compat';
import { stripClaudeCodeEnv } from '../utils/shell-executor';
// Re-export types for consumers // Re-export types for consumers
export type { ExecutionOptions, ExecutionResult, StreamMessage } from './executor/types'; export type { ExecutionOptions, ExecutionResult, StreamMessage } from './executor/types';
@@ -209,7 +210,7 @@ export class HeadlessExecutor {
// Strip Claude Code nested session guard env var to allow CCS delegation // Strip Claude Code nested session guard env var to allow CCS delegation
// (Claude Code v2.1.39+ sets CLAUDECODE to detect nested sessions) // (Claude Code v2.1.39+ sets CLAUDECODE to detect nested sessions)
const { CLAUDECODE: _nested, ...cleanEnv } = process.env; const cleanEnv = stripClaudeCodeEnv(process.env);
const proc = spawn(claudeCli, args, { const proc = spawn(claudeCli, args, {
cwd, cwd,
+18 -2
View File
@@ -24,6 +24,22 @@ export function stripAnthropicEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
return result; return result;
} }
/**
* Strip Claude Code nested-session guard env var from a process environment.
*
* Note: Windows env keys are case-insensitive, so remove case-insensitively
* to avoid missing variants like `claudecode`.
*/
export function stripClaudeCodeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const result: NodeJS.ProcessEnv = {};
for (const key of Object.keys(env)) {
if (key.toUpperCase() !== 'CLAUDECODE') {
result[key] = env[key];
}
}
return result;
}
/** /**
* Escape arguments for shell execution (cross-platform) * Escape arguments for shell execution (cross-platform)
* *
@@ -80,13 +96,13 @@ export function execClaude(
: process.env; : process.env;
// Prepare environment (merge with base env if envVars provided) // Prepare environment (merge with base env if envVars provided)
const env = envVars const mergedEnv = envVars
? { ...baseEnv, ...envVars, ...webSearchEnv } ? { ...baseEnv, ...envVars, ...webSearchEnv }
: { ...baseEnv, ...webSearchEnv }; : { ...baseEnv, ...webSearchEnv };
// Strip Claude Code nested session guard env var to allow CCS delegation // Strip Claude Code nested session guard env var to allow CCS delegation
// (Claude Code v2.1.39+ sets CLAUDECODE to detect nested sessions) // (Claude Code v2.1.39+ sets CLAUDECODE to detect nested sessions)
delete env.CLAUDECODE; const env = stripClaudeCodeEnv(mergedEnv);
// propagate key env vars to tmux session so agent team teammates // propagate key env vars to tmux session so agent team teammates
// (spawned via tmux split-window) inherit the correct config dir // (spawned via tmux split-window) inherit the correct config dir