fix: unblock claude-cli live runs
This commit is contained in:
@@ -177,7 +177,8 @@ CLAWDBOT_LIVE_TEST=1 CLAWDBOT_LIVE_SETUP_TOKEN=1 CLAWDBOT_LIVE_SETUP_TOKEN_PROFI
|
|||||||
- `CLAWDBOT_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-opus-4-5"`
|
- `CLAWDBOT_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-opus-4-5"`
|
||||||
- `CLAWDBOT_LIVE_CLI_BACKEND_COMMAND="/full/path/to/claude"`
|
- `CLAWDBOT_LIVE_CLI_BACKEND_COMMAND="/full/path/to/claude"`
|
||||||
- `CLAWDBOT_LIVE_CLI_BACKEND_ARGS='["-p","--output-format","json","--permission-mode","bypassPermissions"]'`
|
- `CLAWDBOT_LIVE_CLI_BACKEND_ARGS='["-p","--output-format","json","--permission-mode","bypassPermissions"]'`
|
||||||
- `CLAWDBOT_LIVE_CLI_BACKEND_CLEAR_ENV='["ANTHROPIC_API_KEY"]'`
|
- `CLAWDBOT_LIVE_CLI_BACKEND_CLEAR_ENV='["ANTHROPIC_API_KEY","ANTHROPIC_API_KEY_OLD"]'`
|
||||||
|
- `CLAWDBOT_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG=0` to keep Claude CLI MCP config enabled (default disables MCP config with a temporary empty file).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const DEFAULT_CLAUDE_BACKEND: CliBackendConfig = {
|
|||||||
systemPromptArg: "--append-system-prompt",
|
systemPromptArg: "--append-system-prompt",
|
||||||
systemPromptMode: "append",
|
systemPromptMode: "append",
|
||||||
systemPromptWhen: "first",
|
systemPromptWhen: "first",
|
||||||
clearEnv: ["ANTHROPIC_API_KEY"],
|
clearEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"],
|
||||||
serialize: true,
|
serialize: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -455,6 +455,7 @@ export async function runCliAgent(params: {
|
|||||||
backend,
|
backend,
|
||||||
prompt: params.prompt,
|
prompt: params.prompt,
|
||||||
});
|
});
|
||||||
|
const stdinPayload = stdin ?? "";
|
||||||
const args = buildCliArgs({
|
const args = buildCliArgs({
|
||||||
backend,
|
backend,
|
||||||
modelId: normalizedModel,
|
modelId: normalizedModel,
|
||||||
@@ -526,7 +527,7 @@ export async function runCliAgent(params: {
|
|||||||
timeoutMs: params.timeoutMs,
|
timeoutMs: params.timeoutMs,
|
||||||
cwd: workspaceDir,
|
cwd: workspaceDir,
|
||||||
env,
|
env,
|
||||||
...(stdin ? { input: stdin } : {}),
|
input: stdinPayload,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stdout = result.stdout.trim();
|
const stdout = result.stdout.trim();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const DEFAULT_ARGS = [
|
|||||||
"json",
|
"json",
|
||||||
"--dangerously-skip-permissions",
|
"--dangerously-skip-permissions",
|
||||||
];
|
];
|
||||||
const DEFAULT_CLEAR_ENV: string[] = [];
|
const DEFAULT_CLEAR_ENV = ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"];
|
||||||
|
|
||||||
function extractPayloadText(result: unknown): string {
|
function extractPayloadText(result: unknown): string {
|
||||||
const record = result as Record<string, unknown>;
|
const record = result as Record<string, unknown>;
|
||||||
@@ -52,6 +52,20 @@ function parseJsonStringArray(
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withMcpConfigOverrides(
|
||||||
|
args: string[],
|
||||||
|
mcpConfigPath: string,
|
||||||
|
): string[] {
|
||||||
|
const next = [...args];
|
||||||
|
if (!next.includes("--strict-mcp-config")) {
|
||||||
|
next.push("--strict-mcp-config");
|
||||||
|
}
|
||||||
|
if (!next.includes("--mcp-config")) {
|
||||||
|
next.push("--mcp-config", mcpConfigPath);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
async function getFreePort(): Promise<number> {
|
async function getFreePort(): Promise<number> {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const srv = createServer();
|
const srv = createServer();
|
||||||
@@ -134,12 +148,16 @@ describeLive("gateway live (cli backend)", () => {
|
|||||||
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
|
skipGmail: process.env.CLAWDBOT_SKIP_GMAIL_WATCHER,
|
||||||
skipCron: process.env.CLAWDBOT_SKIP_CRON,
|
skipCron: process.env.CLAWDBOT_SKIP_CRON,
|
||||||
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
|
skipCanvas: process.env.CLAWDBOT_SKIP_CANVAS_HOST,
|
||||||
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
||||||
|
anthropicApiKeyOld: process.env.ANTHROPIC_API_KEY_OLD,
|
||||||
};
|
};
|
||||||
|
|
||||||
process.env.CLAWDBOT_SKIP_PROVIDERS = "1";
|
process.env.CLAWDBOT_SKIP_PROVIDERS = "1";
|
||||||
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
|
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
|
||||||
process.env.CLAWDBOT_SKIP_CRON = "1";
|
process.env.CLAWDBOT_SKIP_CRON = "1";
|
||||||
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
|
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
delete process.env.ANTHROPIC_API_KEY_OLD;
|
||||||
|
|
||||||
const token = `test-${randomUUID()}`;
|
const token = `test-${randomUUID()}`;
|
||||||
process.env.CLAWDBOT_GATEWAY_TOKEN = token;
|
process.env.CLAWDBOT_GATEWAY_TOKEN = token;
|
||||||
@@ -156,7 +174,7 @@ describeLive("gateway live (cli backend)", () => {
|
|||||||
|
|
||||||
const cliCommand =
|
const cliCommand =
|
||||||
process.env.CLAWDBOT_LIVE_CLI_BACKEND_COMMAND ?? "claude";
|
process.env.CLAWDBOT_LIVE_CLI_BACKEND_COMMAND ?? "claude";
|
||||||
const cliArgs =
|
const baseCliArgs =
|
||||||
parseJsonStringArray(
|
parseJsonStringArray(
|
||||||
"CLAWDBOT_LIVE_CLI_BACKEND_ARGS",
|
"CLAWDBOT_LIVE_CLI_BACKEND_ARGS",
|
||||||
process.env.CLAWDBOT_LIVE_CLI_BACKEND_ARGS,
|
process.env.CLAWDBOT_LIVE_CLI_BACKEND_ARGS,
|
||||||
@@ -167,6 +185,20 @@ describeLive("gateway live (cli backend)", () => {
|
|||||||
process.env.CLAWDBOT_LIVE_CLI_BACKEND_CLEAR_ENV,
|
process.env.CLAWDBOT_LIVE_CLI_BACKEND_CLEAR_ENV,
|
||||||
) ?? DEFAULT_CLEAR_ENV;
|
) ?? DEFAULT_CLEAR_ENV;
|
||||||
|
|
||||||
|
const tempDir = await fs.mkdtemp(
|
||||||
|
path.join(os.tmpdir(), "clawdbot-live-cli-"),
|
||||||
|
);
|
||||||
|
const mcpConfigPath = path.join(tempDir, "claude-mcp.json");
|
||||||
|
await fs.writeFile(
|
||||||
|
mcpConfigPath,
|
||||||
|
`${JSON.stringify({ mcpServers: {} }, null, 2)}\n`,
|
||||||
|
);
|
||||||
|
const disableMcpConfig =
|
||||||
|
process.env.CLAWDBOT_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG !== "0";
|
||||||
|
const cliArgs = disableMcpConfig
|
||||||
|
? withMcpConfigOverrides(baseCliArgs, mcpConfigPath)
|
||||||
|
: baseCliArgs;
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const existingBackends = cfg.agents?.defaults?.cliBackends ?? {};
|
const existingBackends = cfg.agents?.defaults?.cliBackends ?? {};
|
||||||
const nextCfg = {
|
const nextCfg = {
|
||||||
@@ -185,16 +217,13 @@ describeLive("gateway live (cli backend)", () => {
|
|||||||
command: cliCommand,
|
command: cliCommand,
|
||||||
args: cliArgs,
|
args: cliArgs,
|
||||||
clearEnv: cliClearEnv,
|
clearEnv: cliClearEnv,
|
||||||
|
systemPromptWhen: "never",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sandbox: { mode: "off" },
|
sandbox: { mode: "off" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const tempDir = await fs.mkdtemp(
|
|
||||||
path.join(os.tmpdir(), "clawdbot-live-cli-"),
|
|
||||||
);
|
|
||||||
const tempConfigPath = path.join(tempDir, "clawdbot.json");
|
const tempConfigPath = path.join(tempDir, "clawdbot.json");
|
||||||
await fs.writeFile(tempConfigPath, `${JSON.stringify(nextCfg, null, 2)}\n`);
|
await fs.writeFile(tempConfigPath, `${JSON.stringify(nextCfg, null, 2)}\n`);
|
||||||
process.env.CLAWDBOT_CONFIG_PATH = tempConfigPath;
|
process.env.CLAWDBOT_CONFIG_PATH = tempConfigPath;
|
||||||
@@ -252,6 +281,12 @@ describeLive("gateway live (cli backend)", () => {
|
|||||||
if (previous.skipCanvas === undefined)
|
if (previous.skipCanvas === undefined)
|
||||||
delete process.env.CLAWDBOT_SKIP_CANVAS_HOST;
|
delete process.env.CLAWDBOT_SKIP_CANVAS_HOST;
|
||||||
else process.env.CLAWDBOT_SKIP_CANVAS_HOST = previous.skipCanvas;
|
else process.env.CLAWDBOT_SKIP_CANVAS_HOST = previous.skipCanvas;
|
||||||
|
if (previous.anthropicApiKey === undefined)
|
||||||
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
else process.env.ANTHROPIC_API_KEY = previous.anthropicApiKey;
|
||||||
|
if (previous.anthropicApiKeyOld === undefined)
|
||||||
|
delete process.env.ANTHROPIC_API_KEY_OLD;
|
||||||
|
else process.env.ANTHROPIC_API_KEY_OLD = previous.anthropicApiKeyOld;
|
||||||
}
|
}
|
||||||
}, 60_000);
|
}, 60_000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -59,13 +59,14 @@ export async function runCommandWithTimeout(
|
|||||||
? { timeoutMs: optionsOrTimeout }
|
? { timeoutMs: optionsOrTimeout }
|
||||||
: optionsOrTimeout;
|
: optionsOrTimeout;
|
||||||
const { timeoutMs, cwd, input, env } = options;
|
const { timeoutMs, cwd, input, env } = options;
|
||||||
|
const hasInput = input !== undefined;
|
||||||
|
|
||||||
// Spawn with inherited stdin (TTY) so tools like `pi` stay interactive when needed.
|
// Spawn with inherited stdin (TTY) so tools like `pi` stay interactive when needed.
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const child = spawn(argv[0], argv.slice(1), {
|
const child = spawn(argv[0], argv.slice(1), {
|
||||||
stdio: [input ? "pipe" : "inherit", "pipe", "pipe"],
|
stdio: [hasInput ? "pipe" : "inherit", "pipe", "pipe"],
|
||||||
cwd,
|
cwd,
|
||||||
env: env ? { ...process.env, ...env } : process.env,
|
env: env ?? process.env,
|
||||||
});
|
});
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
@@ -74,8 +75,8 @@ export async function runCommandWithTimeout(
|
|||||||
child.kill("SIGKILL");
|
child.kill("SIGKILL");
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
|
|
||||||
if (input && child.stdin) {
|
if (hasInput && child.stdin) {
|
||||||
child.stdin.write(input);
|
child.stdin.write(input ?? "");
|
||||||
child.stdin.end();
|
child.stdin.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user