From a9bbf45d4bb8dab5488b68016ad6c17372df83e7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 2 Aug 2026 00:20:39 -0700 Subject: [PATCH] refactor(cli): unify config-guard policy across dispatch paths (#117880) --- src/cli/channels-status-cold-imports.test.ts | 4 +- src/cli/command-catalog.ts | 101 ++++++++------- src/cli/command-execution-startup.test.ts | 3 +- src/cli/command-execution-startup.ts | 2 - src/cli/command-path-policy.test.ts | 69 +++++----- src/cli/command-path-policy.ts | 7 +- src/cli/command-startup-policy.test.ts | 127 +++++++++++-------- src/cli/command-startup-policy.ts | 21 +-- src/cli/program/preaction.test.ts | 72 +++++------ src/cli/program/preaction.ts | 5 +- src/cli/route.test.ts | 41 +++--- src/cli/route.ts | 1 - src/cli/run-main.ts | 1 - src/gateway/explicit-connection-policy.ts | 6 - 14 files changed, 238 insertions(+), 222 deletions(-) diff --git a/src/cli/channels-status-cold-imports.test.ts b/src/cli/channels-status-cold-imports.test.ts index ddc56bab6582..112a13aaa527 100644 --- a/src/cli/channels-status-cold-imports.test.ts +++ b/src/cli/channels-status-cold-imports.test.ts @@ -1,4 +1,4 @@ -// The real route-first channel status path must stay out of local config/plugin runtimes. +// The real route-first channel status path must honor the shared catalog guard policy. import { beforeEach, describe, expect, it, vi } from "vitest"; const loaded = vi.hoisted(() => { @@ -55,7 +55,7 @@ describe("routed channels status cold imports", () => { vi.clearAllMocks(); }); - it("keeps successful JSON routes cold with and without probing", async () => { + it("keeps successful JSON routes out of config/plugin runtimes with and without probing", async () => { for (const probe of [false, true]) { vi.clearAllMocks(); const argv = ["node", "openclaw", "channels", "status", "--json"]; diff --git a/src/cli/command-catalog.ts b/src/cli/command-catalog.ts index 2eb48b50ffca..afe381bb1059 100644 --- a/src/cli/command-catalog.ts +++ b/src/cli/command-catalog.ts @@ -6,10 +6,10 @@ export type CliCommandPluginLoadPolicy = | "always" | "text-only" | ((ctx: { argv: string[]; commandPath: string[]; jsonOutputMode: boolean }) => boolean); -type CliRouteConfigGuardPolicy = "never" | "always" | "when-suppressed"; -type CliConfigGuardBypassPolicy = - | boolean - | ((ctx: { argv: string[]; commandPath: string[] }) => boolean); +type CliConfigGuardMode = "run" | "skip" | "when-suppressed"; +type CliConfigGuardPolicy = + | CliConfigGuardMode + | ((ctx: { argv: string[]; commandPath: string[] }) => CliConfigGuardMode); export type CliPluginRegistryScope = "all" | "channels" | "configured-channels"; export type CliPluginRegistryPolicy = { scope: CliPluginRegistryScope; @@ -36,8 +36,7 @@ type CliRoutedCommandId = | "plugins-list"; export type CliCommandPathPolicy = { - bypassConfigGuard: CliConfigGuardBypassPolicy; - routeConfigGuard: CliRouteConfigGuardPolicy; + configGuard: CliConfigGuardPolicy; loadPlugins: CliCommandPluginLoadPolicy; pluginRegistry: CliPluginRegistryPolicy; ownsProtocolStdout: boolean; @@ -72,22 +71,22 @@ function hasCliOption(argv: readonly string[], name: string): boolean { export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["setup"], - policy: { bypassConfigGuard: true, loadPlugins: "never", ensureCliPath: false }, + policy: { configGuard: "skip", loadPlugins: "never", ensureCliPath: false }, }, { commandPath: ["qa"], // Private QA commands create or inspect repo-owned fixtures. They must not // read, validate, migrate, or inherit proxy policy from operator state. - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["crestodian"], // hidden alias - policy: { bypassConfigGuard: true, loadPlugins: "never", ensureCliPath: false }, + policy: { configGuard: "skip", loadPlugins: "never", ensureCliPath: false }, }, { commandPath: ["agent"], policy: { - bypassConfigGuard: ({ argv }) => !hasFlag(argv, "--local"), + configGuard: ({ argv }) => (hasFlag(argv, "--local") ? "run" : "skip"), loadPlugins: ({ argv }) => hasFlag(argv, "--local"), pluginRegistry: { scope: "all" }, networkProxy: ({ argv }) => (hasFlag(argv, "--local") ? "default" : "bypass"), @@ -96,7 +95,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["agent", "exec"], policy: { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", ownsProtocolStdout: true, hideBanner: true, @@ -104,7 +103,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ }, }, { commandPath: ["message"], policy: { loadPlugins: "never" } }, - { commandPath: ["docs"], policy: { bypassConfigGuard: true } }, + { commandPath: ["docs"], policy: { configGuard: "skip" } }, { commandPath: ["channels"], policy: { @@ -117,7 +116,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["agents"], exact: true, - policy: { loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, route: { id: "agents-list" }, }, { @@ -145,24 +144,25 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ exact: true, policy: { loadPlugins: "never" }, }, - { commandPath: ["configure"], policy: { bypassConfigGuard: true, loadPlugins: "never" } }, + { commandPath: ["configure"], policy: { configGuard: "skip", loadPlugins: "never" } }, { commandPath: ["config"], exact: true, - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["config", "models"], exact: true, - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["migrate"], - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["status"], policy: { + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "channels" }, ensureCliPath: false, @@ -173,6 +173,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["health"], policy: { + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "channels" }, ensureCliPath: false, @@ -191,7 +192,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["gateway", "status"], exact: true, policy: { - routeConfigGuard: "always", + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }, @@ -206,7 +207,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ exact: true, // The routed JSON command owns its config read; running the startup guard first // duplicates config/state initialization before the health socket can open. - policy: { routeConfigGuard: "always", networkProxy: "bypass" }, + policy: { configGuard: "skip", networkProxy: "bypass" }, route: { id: "gateway-health" }, }, { commandPath: ["gateway", "install"], exact: true, policy: { networkProxy: "bypass" } }, @@ -220,7 +221,12 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["sessions"], exact: true, - policy: { ensureCliPath: false, ownsProtocolStdout: true, networkProxy: "bypass" }, + policy: { + configGuard: "skip", + ensureCliPath: false, + ownsProtocolStdout: true, + networkProxy: "bypass", + }, route: { id: "sessions" }, }, { @@ -235,7 +241,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["agents", "list"], // Text and JSON output are derived from config plus read-only channel // metadata, so the route should not preload bundled plugin runtimes. - policy: { loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, route: { id: "agents-list" }, }, { @@ -243,7 +249,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ exact: true, // A path query must work before config validation and must not initialize state. policy: { - bypassConfigGuard: true, + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", ownsProtocolStdout: true, @@ -254,8 +260,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["config", "get"], exact: true, policy: { - bypassConfigGuard: true, - routeConfigGuard: "always", + configGuard: "skip", ensureCliPath: false, networkProxy: "bypass", }, @@ -264,13 +269,13 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["config", "unset"], exact: true, - policy: { ensureCliPath: false, networkProxy: "bypass" }, + policy: { configGuard: "run", ensureCliPath: false, networkProxy: "bypass" }, route: { id: "config-unset" }, }, { commandPath: ["models", "list"], exact: true, - policy: { ensureCliPath: false, routeConfigGuard: "always", networkProxy: "bypass" }, + policy: { configGuard: "skip", ensureCliPath: false, networkProxy: "bypass" }, route: { id: "models-list" }, }, { @@ -278,7 +283,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ exact: true, policy: { ensureCliPath: false, - routeConfigGuard: "always", + configGuard: "skip", networkProxy: ({ argv }) => (hasFlag(argv, "--probe") ? "default" : "bypass"), }, route: { id: "models-status" }, @@ -287,6 +292,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["tasks", "list"], exact: true, policy: { + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", @@ -297,6 +303,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["tasks", "audit"], exact: true, policy: { + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", @@ -306,6 +313,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["tasks"], policy: { + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", @@ -328,11 +336,14 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ }, { commandPath: ["approvals"], policy: { networkProxy: "bypass" } }, // automations is a commander alias for cron; argv-derived command paths keep the typed token. - { commandPath: ["automations"], policy: { networkProxy: "bypass" } }, - { commandPath: ["backup"], policy: { bypassConfigGuard: true, networkProxy: "bypass" } }, + { + commandPath: ["automations"], + policy: { configGuard: "skip", networkProxy: "bypass" }, + }, + { commandPath: ["backup"], policy: { configGuard: "skip", networkProxy: "bypass" } }, { commandPath: ["chat"], policy: { networkProxy: "bypass" } }, { commandPath: ["config"], policy: { networkProxy: "bypass" } }, - { commandPath: ["cron"], policy: { networkProxy: "bypass" } }, + { commandPath: ["cron"], policy: { configGuard: "skip", networkProxy: "bypass" } }, { commandPath: ["dashboard"], policy: { networkProxy: "bypass" } }, { commandPath: ["daemon"], policy: { networkProxy: "bypass" } }, { commandPath: ["devices"], policy: { networkProxy: "bypass" } }, @@ -347,7 +358,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["doctor"], policy: { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", // Shared-state maintenance must acquire exclusive ownership before any // config-health observation can open the canonical SQLite database. @@ -387,7 +398,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["worker"], exact: true, policy: { - bypassConfigGuard: true, + configGuard: "skip", hideBanner: true, loadPlugins: "never", ownsProtocolStdout: true, @@ -402,12 +413,12 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["completion"], policy: { - bypassConfigGuard: true, + configGuard: "skip", hideBanner: true, networkProxy: "bypass", }, }, - { commandPath: ["secrets"], policy: { bypassConfigGuard: true, networkProxy: "bypass" } }, + { commandPath: ["secrets"], policy: { configGuard: "skip", networkProxy: "bypass" } }, { commandPath: ["security"], policy: { networkProxy: "bypass" } }, { commandPath: ["system"], policy: { networkProxy: "bypass" } }, { commandPath: ["terminal"], policy: { networkProxy: "bypass" } }, @@ -417,12 +428,12 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["config", "validate"], exact: true, - policy: { bypassConfigGuard: true, networkProxy: "bypass" }, + policy: { configGuard: "skip", networkProxy: "bypass" }, }, { commandPath: ["config", "schema"], exact: true, - policy: { bypassConfigGuard: true, ownsProtocolStdout: true, networkProxy: "bypass" }, + policy: { configGuard: "skip", ownsProtocolStdout: true, networkProxy: "bypass" }, }, { commandPath: ["plugins", "update"], @@ -432,7 +443,12 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["plugins", "list"], exact: true, - policy: { ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass" }, + policy: { + configGuard: "skip", + ensureCliPath: false, + loadPlugins: "never", + networkProxy: "bypass", + }, route: { id: "plugins-list" }, }, { @@ -443,17 +459,17 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["onboard", "recommendations"], exact: true, - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["onboard", "recommendations", "acknowledge"], exact: true, - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["onboard", "recommendations", "refresh"], exact: true, - policy: { bypassConfigGuard: true, loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, }, { commandPath: ["channels", "add"], @@ -485,8 +501,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ commandPath: ["channels", "status"], exact: true, policy: { - bypassConfigGuard: true, - routeConfigGuard: "always", + configGuard: "skip", loadPlugins: "never", networkProxy: ({ argv }) => (hasFlag(argv, "--probe") ? "default" : "bypass"), }, @@ -495,7 +510,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["channels", "list"], exact: true, - policy: { loadPlugins: "never", networkProxy: "bypass" }, + policy: { configGuard: "skip", loadPlugins: "never", networkProxy: "bypass" }, route: { id: "channels-list" }, }, { commandPath: ["skills"], exact: true, policy: { networkProxy: "bypass" } }, diff --git a/src/cli/command-execution-startup.test.ts b/src/cli/command-execution-startup.test.ts index 96c503a20042..b15afaa42dab 100644 --- a/src/cli/command-execution-startup.test.ts +++ b/src/cli/command-execution-startup.test.ts @@ -44,7 +44,6 @@ describe("command-execution-startup", () => { argv: ["node", "openclaw", "status", "--json"], jsonOutputMode: true, env: {}, - routeMode: true, }), ).toEqual({ invocation: { @@ -58,7 +57,7 @@ describe("command-execution-startup", () => { startupPolicy: { suppressDoctorStdout: true, hideBanner: false, - skipConfigGuard: false, + skipConfigGuard: true, loadPlugins: false, pluginRegistry: { scope: "channels" }, }, diff --git a/src/cli/command-execution-startup.ts b/src/cli/command-execution-startup.ts index c5e3a12a52bf..b9271f44ce3d 100644 --- a/src/cli/command-execution-startup.ts +++ b/src/cli/command-execution-startup.ts @@ -19,7 +19,6 @@ export function resolveCliExecutionStartupContext(params: { commandPath?: string[]; jsonOutputMode: boolean; env?: NodeJS.ProcessEnv; - routeMode?: boolean; }) { const invocation = resolveCliArgvInvocation(params.argv); // Commander owns the action path after parsing option values. Route-first @@ -33,7 +32,6 @@ export function resolveCliExecutionStartupContext(params: { commandPath, jsonOutputMode: params.jsonOutputMode, env: params.env, - routeMode: params.routeMode, }), }; } diff --git a/src/cli/command-path-policy.test.ts b/src/cli/command-path-policy.test.ts index 80e0e72d246f..da18bec18f45 100644 --- a/src/cli/command-path-policy.test.ts +++ b/src/cli/command-path-policy.test.ts @@ -8,8 +8,7 @@ import { } from "./command-path-policy.js"; const DEFAULT_EXPECTED_POLICY: CliCommandPathPolicy = { - bypassConfigGuard: false, - routeConfigGuard: "never", + configGuard: "run", loadPlugins: "never", pluginRegistry: { scope: "all" }, ownsProtocolStdout: false, @@ -26,8 +25,8 @@ type LoadPluginsResolver = Extract< CliCommandPathPolicy["loadPlugins"], (ctx: { argv: string[]; commandPath: string[]; jsonOutputMode: boolean }) => unknown >; -type ConfigGuardBypassResolver = Extract< - CliCommandPathPolicy["bypassConfigGuard"], +type ConfigGuardResolver = Extract< + CliCommandPathPolicy["configGuard"], (ctx: { argv: string[]; commandPath: string[] }) => unknown >; @@ -53,10 +52,10 @@ function expectLoadPluginsResolver( expect(typeof policy.loadPlugins).toBe("function"); } -function expectConfigGuardBypassResolver( +function expectConfigGuardResolver( policy: CliCommandPathPolicy, -): asserts policy is CliCommandPathPolicy & { bypassConfigGuard: ConfigGuardBypassResolver } { - expect(typeof policy.bypassConfigGuard).toBe("function"); +): asserts policy is CliCommandPathPolicy & { configGuard: ConfigGuardResolver } { + expect(typeof policy.configGuard).toBe("function"); } describe("command-path-policy", () => { @@ -67,7 +66,7 @@ describe("command-path-policy", () => { it("resolves status policy with shared startup semantics", () => { expectResolvedPolicy(["status"], { - routeConfigGuard: "never", + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "channels" }, ensureCliPath: false, @@ -96,8 +95,7 @@ describe("command-path-policy", () => { const channelsStatusPolicy = resolveCliCommandPathPolicy(["channels", "status"]); expect(channelsStatusPolicy).toEqual({ ...DEFAULT_EXPECTED_POLICY, - bypassConfigGuard: true, - routeConfigGuard: "always", + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "configured-channels" }, networkProxy: channelsStatusPolicy.networkProxy, @@ -116,6 +114,7 @@ describe("command-path-policy", () => { }), ).toBe("default"); expectResolvedPolicy(["channels", "list"], { + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "configured-channels" }, networkProxy: "bypass", @@ -141,13 +140,13 @@ describe("command-path-policy", () => { const agentPolicy = resolveCliCommandPathPolicy(["agent"]); expect(agentPolicy).toEqual({ ...DEFAULT_EXPECTED_POLICY, - bypassConfigGuard: agentPolicy.bypassConfigGuard, + configGuard: agentPolicy.configGuard, loadPlugins: agentPolicy.loadPlugins, pluginRegistry: { scope: "all" }, networkProxy: agentPolicy.networkProxy, }); expectLoadPluginsResolver(agentPolicy); - expectConfigGuardBypassResolver(agentPolicy); + expectConfigGuardResolver(agentPolicy); expectNetworkProxyResolver(agentPolicy); expect( agentPolicy.loadPlugins({ @@ -171,17 +170,17 @@ describe("command-path-policy", () => { }), ).toBe(true); expect( - agentPolicy.bypassConfigGuard({ + agentPolicy.configGuard({ argv: ["node", "openclaw", "agent"], commandPath: ["agent"], }), - ).toBe(true); + ).toBe("skip"); expect( - agentPolicy.bypassConfigGuard({ + agentPolicy.configGuard({ argv: ["node", "openclaw", "agent", "--local"], commandPath: ["agent"], }), - ).toBe(false); + ).toBe("run"); expect( agentPolicy.networkProxy({ argv: ["node", "openclaw", "agent"], @@ -196,7 +195,7 @@ describe("command-path-policy", () => { ).toBe("default"); expectResolvedPolicy(["agent", "exec"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", pluginRegistry: { scope: "all" }, ownsProtocolStdout: true, @@ -204,9 +203,14 @@ describe("command-path-policy", () => { networkProxy: "default", }); + for (const commandPath of [["agents"], ["agents", "list"]]) { + expectResolvedPolicy(commandPath, { + configGuard: "skip", + loadPlugins: "never", + networkProxy: "bypass", + }); + } for (const commandPath of [ - ["agents"], - ["agents", "list"], ["agents", "bind"], ["agents", "bindings"], ["agents", "unbind"], @@ -222,28 +226,28 @@ describe("command-path-policy", () => { it("resolves mixed startup-only rules", () => { expectResolvedPolicy(["qa", "suite"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); expectResolvedPolicy(["worker"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", hideBanner: true, ownsProtocolStdout: true, networkProxy: "bypass", }); expectResolvedPolicy(["configure"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", }); expectResolvedPolicy(["config"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); expectResolvedPolicy(["config", "file"], { - bypassConfigGuard: true, + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", ownsProtocolStdout: true, @@ -256,7 +260,7 @@ describe("command-path-policy", () => { const doctorPolicy = resolveCliCommandPathPolicy(["doctor"]); expectNetworkProxyResolver(doctorPolicy); expect(doctorPolicy).toMatchObject({ - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", }); expect( @@ -272,23 +276,23 @@ describe("command-path-policy", () => { }), ).toBe("bypass"); expectResolvedPolicy(["config", "validate"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); expectResolvedPolicy(["config", "schema"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", ownsProtocolStdout: true, networkProxy: "bypass", }); expectResolvedPolicy(["gateway", "status"], { - routeConfigGuard: "always", + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); expectResolvedPolicy(["gateway", "health"], { - routeConfigGuard: "always", + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); @@ -297,12 +301,14 @@ describe("command-path-policy", () => { hideBanner: true, }); expectResolvedPolicy(["plugins", "list"], { + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", }); for (const commandPath of [["tasks"], ["tasks", "list"], ["tasks", "audit"]]) { expectResolvedPolicy(commandPath, { + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", @@ -319,7 +325,7 @@ describe("command-path-policy", () => { }); } expectResolvedPolicy(["cron", "list"], { - bypassConfigGuard: true, + configGuard: "skip", loadPlugins: "never", networkProxy: "bypass", }); @@ -327,8 +333,7 @@ describe("command-path-policy", () => { it("keeps routed and Commander config reads ahead of observing startup guards", () => { expectResolvedPolicy(["config", "get"], { - bypassConfigGuard: true, - routeConfigGuard: "always", + configGuard: "skip", ensureCliPath: false, loadPlugins: "never", networkProxy: "bypass", diff --git a/src/cli/command-path-policy.ts b/src/cli/command-path-policy.ts index 0425696b30c1..302c1e0697c6 100644 --- a/src/cli/command-path-policy.ts +++ b/src/cli/command-path-policy.ts @@ -1,6 +1,5 @@ import { expectDefined } from "@openclaw/normalization-core"; // Resolves CLI command path policy from the declarative command catalog. -import { isGatewayConfigBypassCommandPath } from "../gateway/explicit-connection-policy.js"; import { resolveCliStartupCommandPath } from "./argv-invocation.js"; import { getCommandPathWithRootOptions } from "./argv.js"; import { @@ -12,8 +11,7 @@ import { matchesCommandPath } from "./command-path-matches.js"; import { resolveGatewayCatalogCommandPath } from "./gateway-run-argv.js"; const DEFAULT_CLI_COMMAND_PATH_POLICY: CliCommandPathPolicy = { - bypassConfigGuard: false, - routeConfigGuard: "never", + configGuard: "run", loadPlugins: "never", pluginRegistry: { scope: "all" }, ownsProtocolStdout: false, @@ -34,9 +32,6 @@ export function resolveCliCommandPathPolicy(commandPath: string[]): CliCommandPa } Object.assign(resolvedPolicy, entry.policy); } - if (isGatewayConfigBypassCommandPath(commandPath)) { - resolvedPolicy.bypassConfigGuard = true; - } return resolvedPolicy; } diff --git a/src/cli/command-startup-policy.test.ts b/src/cli/command-startup-policy.test.ts index 73435f0a7454..158ea45df9eb 100644 --- a/src/cli/command-startup-policy.test.ts +++ b/src/cli/command-startup-policy.test.ts @@ -1,16 +1,15 @@ // Command startup policy tests cover which CLI commands require startup side effects. -import { describe, expect, it } from "vitest"; -import { - resolveCliStartupPolicy, - shouldBypassConfigGuardForCommandPath, -} from "./command-startup-policy.js"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cliCommandCatalog } from "./command-catalog.js"; +import { resolveCliExecutionStartupContext } from "./command-execution-startup.js"; +import { resolveCliStartupPolicy } from "./command-startup-policy.js"; function resolvePolicy(params: { argv?: string[]; commandPath: string[]; jsonOutputMode?: boolean; env?: NodeJS.ProcessEnv; - routeMode?: boolean; }) { return resolveCliStartupPolicy({ jsonOutputMode: false, @@ -19,44 +18,83 @@ function resolvePolicy(params: { } describe("command-startup-policy", () => { - it("matches config guard bypass commands", () => { - expect(shouldBypassConfigGuardForCommandPath(["backup", "create"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["config"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["config", "file"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["config", "validate"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["config", "schema"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["docs"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["agent", "exec"])).toBe(true); - expect(shouldBypassConfigGuardForCommandPath(["agent"], ["node", "openclaw", "agent"])).toBe( - true, - ); - expect( - shouldBypassConfigGuardForCommandPath(["agent"], ["node", "openclaw", "agent", "--local"]), - ).toBe(false); - expect(shouldBypassConfigGuardForCommandPath(["config", "set"])).toBe(false); - expect(shouldBypassConfigGuardForCommandPath(["status"])).toBe(false); + afterEach(() => { + vi.doUnmock("./command-path-policy.js"); + vi.resetModules(); }); - it("matches route-first config guard skip policy", () => { + it("resolves config guard policy for Commander and invocation-aware commands", () => { + for (const commandPath of [ + ["backup", "create"], + ["config"], + ["config", "file"], + ["config", "validate"], + ["config", "schema"], + ["docs"], + ["agent", "exec"], + ["status"], + ]) { + expect(resolvePolicy({ commandPath }).skipConfigGuard, commandPath.join(" ")).toBe(true); + } expect( resolvePolicy({ - commandPath: ["status"], - jsonOutputMode: true, - routeMode: true, - }).skipConfigGuard, - ).toBe(false); - expect( - resolvePolicy({ - commandPath: ["gateway", "status"], - routeMode: true, + argv: ["node", "openclaw", "agent"], + commandPath: ["agent"], }).skipConfigGuard, ).toBe(true); expect( resolvePolicy({ - commandPath: ["status"], - routeMode: true, + argv: ["node", "openclaw", "agent", "--local"], + commandPath: ["agent"], }).skipConfigGuard, ).toBe(false); + expect(resolvePolicy({ commandPath: ["config", "set"] }).skipConfigGuard).toBe(false); + }); + + it("keeps every route-first command on the same config guard declaration as Commander", () => { + for (const entry of cliCommandCatalog.filter((candidate) => candidate.route)) { + expect(entry.policy?.configGuard, entry.commandPath.join(" ")).toBeDefined(); + for (const jsonOutputMode of [false, true]) { + const argv = ["node", "openclaw", ...entry.commandPath]; + const expectedSkip = entry.commandPath.join(" ") !== "config unset"; + const routed = resolveCliExecutionStartupContext({ argv, jsonOutputMode }); + const commander = resolveCliExecutionStartupContext({ + argv, + commandPath: [...entry.commandPath], + jsonOutputMode, + }); + expect(routed.startupPolicy.skipConfigGuard, entry.commandPath.join(" ")).toBe( + commander.startupPolicy.skipConfigGuard, + ); + expect(routed.startupPolicy.skipConfigGuard, entry.commandPath.join(" ")).toBe( + expectedSkip, + ); + } + } + }); + + it("skips when-suppressed guards only for suppressed output", async () => { + vi.doMock("./command-path-policy.js", () => ({ + resolveCliCommandPathPolicy: () => ({ + configGuard: "when-suppressed", + loadPlugins: "never", + pluginRegistry: { scope: "all" }, + ownsProtocolStdout: false, + hideBanner: false, + ensureCliPath: true, + networkProxy: "default", + }), + })); + const { resolveCliStartupPolicy: resolveWithSuppressedGuard } = await importFreshModule< + typeof import("./command-startup-policy.js") + >(import.meta.url, "./command-startup-policy.js?when-suppressed"); + + expect( + resolveWithSuppressedGuard({ commandPath: ["test"], jsonOutputMode: false }).skipConfigGuard, + ).toBe(false); + expect( + resolveWithSuppressedGuard({ commandPath: ["test"], jsonOutputMode: true }).skipConfigGuard, + ).toBe(true); }); it("matches plugin preload policy", () => { @@ -220,7 +258,7 @@ describe("command-startup-policy", () => { } }); - it("aggregates startup policy for commander and route-first callers", () => { + it("aggregates startup policy for both dispatch paths", () => { expect( resolveCliStartupPolicy({ commandPath: ["status"], @@ -230,22 +268,7 @@ describe("command-startup-policy", () => { ).toEqual({ suppressDoctorStdout: true, hideBanner: false, - skipConfigGuard: false, - loadPlugins: false, - pluginRegistry: { scope: "channels" }, - }); - - expect( - resolveCliStartupPolicy({ - commandPath: ["status"], - jsonOutputMode: true, - env: {}, - routeMode: true, - }), - ).toEqual({ - suppressDoctorStdout: true, - hideBanner: false, - skipConfigGuard: false, + skipConfigGuard: true, loadPlugins: false, pluginRegistry: { scope: "channels" }, }); @@ -266,7 +289,7 @@ describe("command-startup-policy", () => { it("isolates cloud worker startup", () => { const policy = resolvePolicy({ commandPath: ["worker"] }); - expect(shouldBypassConfigGuardForCommandPath(["worker"])).toBe(true); + expect(policy.skipConfigGuard).toBe(true); expect(policy.hideBanner).toBe(true); expect(policy.loadPlugins).toBe(false); expect(policy.suppressDoctorStdout).toBe(true); diff --git a/src/cli/command-startup-policy.ts b/src/cli/command-startup-policy.ts index 97a6c421b50b..0aab95bbd8d7 100644 --- a/src/cli/command-startup-policy.ts +++ b/src/cli/command-startup-policy.ts @@ -3,16 +3,6 @@ import { isTruthyEnvValue } from "../infra/env.js"; import type { CliCommandPluginLoadPolicy } from "./command-catalog.js"; import { resolveCliCommandPathPolicy } from "./command-path-policy.js"; -export function shouldBypassConfigGuardForCommandPath( - commandPath: string[], - argv: string[] = [], -): boolean { - const bypassConfigGuard = resolveCliCommandPathPolicy(commandPath).bypassConfigGuard; - return typeof bypassConfigGuard === "function" - ? bypassConfigGuard({ argv, commandPath }) - : bypassConfigGuard; -} - function shouldLoadPlugins(params: { argv?: string[]; commandPath: string[]; @@ -36,19 +26,20 @@ export function resolveCliStartupPolicy(params: { commandPath: string[]; jsonOutputMode: boolean; env?: NodeJS.ProcessEnv; - routeMode?: boolean; }) { const commandPolicy = resolveCliCommandPathPolicy(params.commandPath); // Protocol commands own stdout from process startup, before their action installs later routing. const suppressDoctorStdout = params.jsonOutputMode || commandPolicy.ownsProtocolStdout; + const configGuard = + typeof commandPolicy.configGuard === "function" + ? commandPolicy.configGuard({ argv: params.argv ?? [], commandPath: params.commandPath }) + : commandPolicy.configGuard; const env = params.env ?? process.env; return { suppressDoctorStdout, hideBanner: isTruthyEnvValue(env.OPENCLAW_HIDE_BANNER) || commandPolicy.hideBanner, - skipConfigGuard: params.routeMode - ? commandPolicy.routeConfigGuard === "always" || - (commandPolicy.routeConfigGuard === "when-suppressed" && suppressDoctorStdout) - : false, + skipConfigGuard: + configGuard === "skip" || (configGuard === "when-suppressed" && suppressDoctorStdout), loadPlugins: shouldLoadPlugins({ argv: params.argv, commandPath: params.commandPath, diff --git a/src/cli/program/preaction.test.ts b/src/cli/program/preaction.test.ts index d5d09d504f7c..5e92eb16b410 100644 --- a/src/cli/program/preaction.test.ts +++ b/src/cli/program/preaction.test.ts @@ -270,6 +270,10 @@ describe("registerPreActionHooks", () => { .option("--json") .action(() => {}); config.command("file").action(() => {}); + config + .command("unset") + .argument("") + .action(() => {}); config .command("validate") .option("--json") @@ -300,7 +304,7 @@ describe("registerPreActionHooks", () => { await preActionHook(program, actionCommand); } - it("handles debug mode and config-only command preaction", async () => { + it("applies shared skip policy to routed reads on the Commander path", async () => { const processTitleSetSpy = vi.spyOn(process, "title", "set"); await runPreAction({ parseArgv: ["status"], @@ -309,11 +313,7 @@ describe("registerPreActionHooks", () => { expect(emitCliBannerMock).toHaveBeenCalledWith("9.9.9-test"); expect(setVerboseMock).toHaveBeenCalledWith(true); - expect(ensureConfigReadyMock).toHaveBeenCalledWith({ - runtime: runtimeMock, - measure: expect.any(Function), - commandPath: ["status"], - }); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); expect(processTitleSetSpy).toHaveBeenCalledWith("openclaw-status"); @@ -325,11 +325,7 @@ describe("registerPreActionHooks", () => { expect(setVerboseMock).toHaveBeenCalledWith(false); expect(process.env.NODE_NO_WARNINGS).toBe("1"); - expect(ensureConfigReadyMock).toHaveBeenCalledWith({ - runtime: runtimeMock, - measure: expect.any(Function), - commandPath: ["agents", "list"], - }); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); processTitleSetSpy.mockRestore(); }); @@ -690,7 +686,7 @@ describe("registerPreActionHooks", () => { }); expect(emitCliBannerMock).not.toHaveBeenCalled(); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); }); it("bootstraps when Commander consumed --help as a required option value", async () => { @@ -754,12 +750,7 @@ describe("registerPreActionHooks", () => { processArgv: ["node", "openclaw", "status", "--json"], }); - expect(ensureConfigReadyMock).toHaveBeenCalledWith({ - runtime: runtimeMock, - measure: expect.any(Function), - commandPath: ["status"], - suppressDoctorStdout: true, - }); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); vi.clearAllMocks(); @@ -921,22 +912,9 @@ describe("registerPreActionHooks", () => { }); }); - it.each([ - { - name: "keeps a remote call migration-free", - argv: ["node", "openclaw", "gateway", "--token", "secret", "call", "health", "--json"], - expectedPath: ["gateway", "call"], - expectedMigration: false, - }, - { - name: "keeps health on the invalid-config allowlist", - argv: ["node", "openclaw", "gateway", "--port", "19083", "health", "--json"], - expectedPath: ["gateway", "health"], - expectedMigration: true, - }, - ])("uses the Commander path past parent option values and $name", async (testCase) => { + it("uses the Commander path past parent option values for gateway calls", async () => { const parseProgram = buildProgram(); - process.argv = testCase.argv; + process.argv = ["node", "openclaw", "gateway", "--token", "secret", "call", "health", "--json"]; await parseProgram.parseAsync(process.argv); @@ -944,12 +922,19 @@ describe("registerPreActionHooks", () => { expect(bootstrap).toEqual({ runtime: runtimeMock, measure: expect.any(Function), - commandPath: testCase.expectedPath, + commandPath: ["gateway", "call"], suppressDoctorStdout: true, }); - expect(shouldMigrateStateFromPath(bootstrap?.commandPath ?? [])).toBe( - testCase.expectedMigration, - ); + expect(shouldMigrateStateFromPath(bootstrap?.commandPath ?? [])).toBe(false); + }); + + it("uses the shared skip policy for gateway health on the Commander path", async () => { + const parseProgram = buildProgram(); + process.argv = ["node", "openclaw", "gateway", "--port", "19083", "health", "--json"]; + + await parseProgram.parseAsync(process.argv); + + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); }); it("does not preload plugins for agents list JSON output", async () => { @@ -1026,6 +1011,19 @@ describe("registerPreActionHooks", () => { expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); }); + it("keeps config guard for config unset mutations", async () => { + await runPreAction({ + parseArgv: ["config", "unset", "gateway.port"], + processArgv: ["node", "openclaw", "config", "unset", "gateway.port"], + }); + + expect(ensureConfigReadyMock).toHaveBeenCalledWith({ + runtime: runtimeMock, + measure: expect.any(Function), + commandPath: ["config", "unset"], + }); + }); + it("bypasses config guard for backup create", async () => { await runPreAction({ parseArgv: ["backup", "create"], diff --git a/src/cli/program/preaction.ts b/src/cli/program/preaction.ts index 454f9fc8d6ff..a9b02d2503b3 100644 --- a/src/cli/program/preaction.ts +++ b/src/cli/program/preaction.ts @@ -12,7 +12,6 @@ import { ensureCliExecutionBootstrap, resolveCliExecutionStartupContext, } from "../command-execution-startup.js"; -import { shouldBypassConfigGuardForCommandPath } from "../command-startup-policy.js"; import { applyResolvedCommandOutputMode } from "../json-output-mode.js"; import { resolvePluginInstallInvalidConfigPolicy, @@ -125,7 +124,6 @@ export function registerPreActionHooks(program: Command, programVersion: string) jsonOutputMode, env: process.env, }); - const bypassConfigGuard = shouldBypassConfigGuardForCommandPath(commandPath, argv); await applyCliExecutionStartupPresentation({ startupPolicy, version: programVersion, @@ -140,7 +138,7 @@ export function registerPreActionHooks(program: Command, programVersion: string) process.env.NODE_NO_WARNINGS ??= "1"; } if ( - bypassConfigGuard || + startupPolicy.skipConfigGuard || isGuidedConfigAction(actionCommand) || isGuidedConfigCommandPath(commandPath) ) { @@ -185,7 +183,6 @@ export function registerPreActionHooks(program: Command, programVersion: string) ...(beforeStateMigrations ? { beforeStateMigrations } : {}), ...(skipPristineStartupStateMigrations ? { skipPristineStartupStateMigrations: true } : {}), ...(skipPristineCoreStateMigrations ? { skipPristineCoreStateMigrations: true } : {}), - skipConfigGuard: bypassConfigGuard, }); if (beforeStateMigrations) { const { reloadTrustedGatewayRunEnvironment } = diff --git a/src/cli/route.test.ts b/src/cli/route.test.ts index 04796c8d09d5..e5b146591f6b 100644 --- a/src/cli/route.test.ts +++ b/src/cli/route.test.ts @@ -92,29 +92,24 @@ describe("tryRouteCli", () => { } }); - it("keeps config guard for routed status --json commands", async () => { + it("skips config guard for routed status --json commands", async () => { await expect(tryRouteCli(["node", "openclaw", "status", "--json"])).resolves.toBe(true); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); - expect(firstConfigReadyCall()?.commandPath).toEqual(["status"]); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); }); - it("keeps config guard for the parent tasks JSON list alias", async () => { + it("skips config guard for the parent tasks JSON list alias", async () => { await expect(tryRouteCli(["node", "openclaw", "tasks", "--json"])).resolves.toBe(true); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); - expect(firstConfigReadyCall()?.commandPath).toEqual(["tasks"]); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); }); - it("does not pass suppressDoctorStdout for routed non-json commands", async () => { + it("skips config guard but still loads requested plugins for routed text commands", async () => { await expect(tryRouteCli(["node", "openclaw", "status"])).resolves.toBe(true); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); - const configReadyCall = firstConfigReadyCall(); - expect(typeof configReadyCall?.runtime).toBe("object"); - expect(configReadyCall?.commandPath).toEqual(["status"]); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels", }); @@ -146,6 +141,15 @@ describe("tryRouteCli", () => { expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled(); }); + it("keeps config guard for routed config mutations", async () => { + await expect( + tryRouteCli(["node", "openclaw", "config", "unset", "gateway.port"]), + ).resolves.toBe(true); + + expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); + expect(firstConfigReadyCall()?.commandPath).toEqual(["config", "unset"]); + }); + it("keeps logs routed to stderr for routed --json commands", async () => { findRoutedCommandMock.mockReturnValue({ loadPlugins: true, @@ -201,8 +205,9 @@ describe("tryRouteCli", () => { it("routes status when root options precede the command", async () => { const capturedLogLevels: Array = []; - ensureConfigReadyMock.mockImplementationOnce(async () => { + runRouteMock.mockImplementationOnce(async () => { capturedLogLevels.push(process.env.OPENCLAW_LOG_LEVEL); + return true; }); await expect(tryRouteCli(["node", "openclaw", "--log-level", "debug", "status"])).resolves.toBe( @@ -213,10 +218,7 @@ describe("tryRouteCli", () => { ["status"], ["node", "openclaw", "--log-level", "debug", "status"], ); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); - const configReadyCall = firstConfigReadyCall(); - expect(typeof configReadyCall?.runtime).toBe("object"); - expect(configReadyCall?.commandPath).toEqual(["status"]); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels", }); @@ -226,15 +228,16 @@ describe("tryRouteCli", () => { it("applies routed log level options after the command", async () => { const capturedLogLevels: Array = []; - ensureConfigReadyMock.mockImplementationOnce(async () => { + runRouteMock.mockImplementationOnce(async () => { capturedLogLevels.push(process.env.OPENCLAW_LOG_LEVEL); + return true; }); await expect(tryRouteCli(["node", "openclaw", "status", "--log-level=trace"])).resolves.toBe( true, ); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(runRouteMock).toHaveBeenCalledTimes(1); expect(capturedLogLevels).toEqual(["trace"]); expect(process.env.OPENCLAW_LOG_LEVEL).toBe("trace"); @@ -245,7 +248,7 @@ describe("tryRouteCli", () => { tryRouteCli(["node", "openclaw", "--log-level", "debug", "status", "--log-level=trace"]), ).resolves.toBe(true); - expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); + expect(ensureConfigReadyMock).not.toHaveBeenCalled(); expect(runRouteMock).toHaveBeenCalledTimes(1); expect(process.env.OPENCLAW_LOG_LEVEL).toBe("trace"); }); diff --git a/src/cli/route.ts b/src/cli/route.ts index 7b71613f5401..fed6e9741950 100644 --- a/src/cli/route.ts +++ b/src/cli/route.ts @@ -59,7 +59,6 @@ async function prepareRoutedCommand(params: { argv: params.argv, jsonOutputMode: params.machineOutput === true || hasFlag(params.argv, "--json"), env: process.env, - routeMode: true, }); const { VERSION } = await import("../version.js"); await applyCliExecutionStartupPresentation({ diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 455da5ae2e89..8dc36c7238cb 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -186,7 +186,6 @@ async function tryRunGatewayRunFastPath( argv, commandPath, jsonOutputMode: hasJsonOutputFlag(argv), - routeMode: true, }); if (!startupPolicy.hideBanner) { emitCliBanner(VERSION, { argv }); diff --git a/src/gateway/explicit-connection-policy.ts b/src/gateway/explicit-connection-policy.ts index c320145726ab..d2ddd627d791 100644 --- a/src/gateway/explicit-connection-policy.ts +++ b/src/gateway/explicit-connection-policy.ts @@ -22,9 +22,3 @@ export function canSkipGatewayConfigLoad(params: { hasExplicitGatewayConnectionAuth(params.explicitAuth) ); } - -/** Returns true for command families that intentionally bypass gateway config loading. */ -export function isGatewayConfigBypassCommandPath(commandPath: readonly string[]): boolean { - // Command paths come from raw argv, so the automations alias keeps its typed token. - return commandPath[0] === "cron" || commandPath[0] === "automations"; -}