fix(cli): keep gateway run on fast path

This commit is contained in:
Vincent Koc
2026-04-27 20:22:24 -07:00
parent 758262e1e3
commit c205577f2c
3 changed files with 162 additions and 19 deletions

View File

@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Gateway/startup: keep value-option foreground starts on the gateway fast path and skip proxy bootstrap unless proxy env is configured, reducing normal gateway startup RSS and avoiding full CLI graph loading. Thanks @vincentkoc.
- Backup: skip installed plugin `extensions/*/node_modules` dependency trees while keeping plugin manifests and source files in archives, so local backups avoid rebuildable npm payload bloat. Fixes #64144. Thanks @BrilliantWang.
- Cron/models: fail isolated cron runs closed when an explicit `payload.model` is not allowed or cannot be resolved, so scheduled jobs do not silently fall back to an unrelated agent default or paid route before configured provider proxies such as LiteLLM can run. Fixes #73146. Thanks @oneandrewwang.
- Memory/QMD: back off repeated chat-turn QMD open failures while still letting memory status and CLI probes recheck immediately, so a broken sidecar dependency cannot trigger active-memory or cron retry storms. Fixes #73188 and #73176. Thanks @leonlushgit and @w3i-William.

View File

@@ -33,10 +33,19 @@ describe("isGatewayRunFastPathArgv", () => {
it("matches only plain gateway foreground starts without root options or help", () => {
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway"])).toBe(true);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--force"])).toBe(true);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--port", "18789"])).toBe(true);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--auth=none"])).toBe(true);
expect(
isGatewayRunFastPathArgv(["node", "openclaw", "--no-color", "gateway", "--bind", "loopback"]),
).toBe(true);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "run"])).toBe(true);
expect(
isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "run", "--raw-stream-path", "x"]),
).toBe(true);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "call", "health"])).toBe(false);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--help"])).toBe(false);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "--no-color", "gateway"])).toBe(false);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--port"])).toBe(false);
expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway", "--unknown"])).toBe(false);
});
});

View File

@@ -4,6 +4,7 @@ import process from "node:process";
import { fileURLToPath } from "node:url";
import { resolveStateDir } from "../config/paths.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isValueToken } from "../infra/cli-root-options.js";
import { isTruthyEnvValue, normalizeEnv } from "../infra/env.js";
import { isMainModule } from "../infra/is-main.js";
import { ensureOpenClawCliOnPath } from "../infra/path-env.js";
@@ -39,6 +40,40 @@ export {
type Awaitable<T> = T | Promise<T>;
const GATEWAY_RUN_VALUE_FLAGS = new Set([
"--port",
"--bind",
"--token",
"--auth",
"--password",
"--password-file",
"--tailscale",
"--ws-log",
"--raw-stream-path",
]);
const GATEWAY_RUN_BOOLEAN_FLAGS = new Set([
"--tailscale-reset-on-exit",
"--allow-unconfigured",
"--dev",
"--reset",
"--force",
"--verbose",
"--cli-backend-logs",
"--claude-cli-logs",
"--compact",
"--raw-stream",
]);
const CLI_PROXY_ENV_KEYS = [
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"http_proxy",
"https_proxy",
"all_proxy",
] as const;
function createGatewayCliMainStartupTrace(argv: string[]) {
const enabled =
isTruthyEnvValue(process.env.OPENCLAW_GATEWAY_STARTUP_TRACE) &&
@@ -73,14 +108,45 @@ function createGatewayCliMainStartupTrace(argv: string[]) {
}
export function isGatewayRunFastPathArgv(argv: string[]): boolean {
if (argv[2] !== "gateway") {
return false;
}
const invocation = resolveCliArgvInvocation(argv);
if (invocation.hasHelpOrVersion || invocation.commandPath[0] !== "gateway") {
if (invocation.hasHelpOrVersion) {
return false;
}
return invocation.commandPath.length === 1 || invocation.commandPath[1] === "run";
const args = argv.slice(2);
let sawGateway = false;
let sawRun = false;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (!arg || arg === "--") {
return false;
}
if (!sawGateway) {
const consumed = consumeGatewayFastPathRootOptionToken(args, index);
if (consumed > 0) {
index += consumed - 1;
continue;
}
if (arg !== "gateway") {
return false;
}
sawGateway = true;
continue;
}
const consumed = consumeGatewayRunOptionToken(args, index);
if (consumed > 0) {
index += consumed - 1;
continue;
}
if (!sawRun && arg === "run") {
sawRun = true;
continue;
}
return false;
}
return sawGateway;
}
function hasJsonOutputFlag(argv: string[]): boolean {
@@ -121,6 +187,7 @@ async function tryRunGatewayRunFastPath(
const program = new Command();
program.name("openclaw");
program.enablePositionalOptions();
program.option("--no-color", "Disable ANSI colors", false);
program.exitOverride((err) => {
process.exitCode = typeof err.exitCode === "number" ? err.exitCode : 1;
throw err;
@@ -201,6 +268,72 @@ async function ensureCliEnvProxyDispatcher(): Promise<void> {
}
}
function consumeGatewayRunOptionToken(args: ReadonlyArray<string>, index: number): number {
const arg = args[index];
if (!arg || arg === "--" || !arg.startsWith("-")) {
return 0;
}
const equalsIndex = arg.indexOf("=");
const flag = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
if (GATEWAY_RUN_BOOLEAN_FLAGS.has(flag)) {
return equalsIndex === -1 ? 1 : 0;
}
if (!GATEWAY_RUN_VALUE_FLAGS.has(flag)) {
return 0;
}
if (equalsIndex !== -1) {
return arg.slice(equalsIndex + 1).trim() ? 1 : 0;
}
return isValueToken(args[index + 1]) ? 2 : 0;
}
function consumeGatewayFastPathRootOptionToken(args: ReadonlyArray<string>, index: number): number {
const arg = args[index];
if (!arg || arg === "--") {
return 0;
}
if (arg === "--no-color") {
return 1;
}
if (arg.startsWith("--profile=")) {
return arg.slice("--profile=".length).trim() ? 1 : 0;
}
if (arg === "--profile") {
return isValueToken(args[index + 1]) ? 2 : 0;
}
return 0;
}
function shouldBootstrapCliProxyBeforeFastPath(env: NodeJS.ProcessEnv = process.env): boolean {
if (
isTruthyEnvValue(env.OPENCLAW_DEBUG_PROXY_ENABLED) ||
isTruthyEnvValue(env.OPENCLAW_DEBUG_PROXY_REQUIRE)
) {
return true;
}
return CLI_PROXY_ENV_KEYS.some((key) => {
const value = env[key];
return typeof value === "string" && value.trim().length > 0;
});
}
async function bootstrapCliProxyCaptureAndDispatcher(
startupTrace: ReturnType<typeof createGatewayCliMainStartupTrace>,
): Promise<void> {
const [
{ initializeDebugProxyCapture, finalizeDebugProxyCapture },
{ maybeWarnAboutDebugProxyCoverage },
] = await startupTrace.measure("proxy-imports", () =>
Promise.all([import("../proxy-capture/runtime.js"), import("../proxy-capture/coverage.js")]),
);
initializeDebugProxyCapture("cli");
process.once("exit", () => {
finalizeDebugProxyCapture();
});
await startupTrace.measure("proxy-dispatcher", () => ensureCliEnvProxyDispatcher());
maybeWarnAboutDebugProxyCoverage();
}
export async function runCli(argv: string[] = process.argv) {
const originalArgv = normalizeWindowsArgv(argv);
const startupTrace = createGatewayCliMainStartupTrace(originalArgv);
@@ -312,20 +445,20 @@ export async function runCli(argv: string[] = process.argv) {
return;
}
const [
{ initializeDebugProxyCapture, finalizeDebugProxyCapture },
{ maybeWarnAboutDebugProxyCoverage },
] = await startupTrace.measure("proxy-imports", () =>
Promise.all([import("../proxy-capture/runtime.js"), import("../proxy-capture/coverage.js")]),
);
initializeDebugProxyCapture("cli");
process.once("exit", () => {
finalizeDebugProxyCapture();
});
await startupTrace.measure("proxy-dispatcher", () => ensureCliEnvProxyDispatcher());
maybeWarnAboutDebugProxyCoverage();
const bootstrapProxyBeforeFastPath = shouldBootstrapCliProxyBeforeFastPath();
if (
!bootstrapProxyBeforeFastPath &&
(await tryRunGatewayRunFastPath(normalizedArgv, startupTrace))
) {
return;
}
if (await tryRunGatewayRunFastPath(normalizedArgv, startupTrace)) {
await bootstrapCliProxyCaptureAndDispatcher(startupTrace);
if (
bootstrapProxyBeforeFastPath &&
(await tryRunGatewayRunFastPath(normalizedArgv, startupTrace))
) {
return;
}