From 13ae897cfbd7c37b3bc5e0ab207b65b5ef2043e8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Jul 2026 08:02:04 +0100 Subject: [PATCH] fix(cli): keep config schema stdout parseable (#110496) --- src/cli/command-catalog.ts | 2 +- src/cli/command-path-policy.test.ts | 6 +++ src/config/schema.hints.ts | 12 ----- test/cli-json-stdout.e2e.test.ts | 80 +++++++++++++++++++++++------ 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/cli/command-catalog.ts b/src/cli/command-catalog.ts index 42a0a001ba5..a9aa66ac992 100644 --- a/src/cli/command-catalog.ts +++ b/src/cli/command-catalog.ts @@ -374,7 +374,7 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [ { commandPath: ["config", "schema"], exact: true, - policy: { bypassConfigGuard: true, networkProxy: "bypass" }, + policy: { bypassConfigGuard: true, ownsProtocolStdout: true, networkProxy: "bypass" }, }, { commandPath: ["plugins", "update"], diff --git a/src/cli/command-path-policy.test.ts b/src/cli/command-path-policy.test.ts index 36911f8c3b4..5073859694f 100644 --- a/src/cli/command-path-policy.test.ts +++ b/src/cli/command-path-policy.test.ts @@ -229,6 +229,12 @@ describe("command-path-policy", () => { loadPlugins: "never", networkProxy: "bypass", }); + expectResolvedPolicy(["config", "schema"], { + bypassConfigGuard: true, + loadPlugins: "never", + ownsProtocolStdout: true, + networkProxy: "bypass", + }); expectResolvedPolicy(["gateway", "status"], { routeConfigGuard: "always", loadPlugins: "never", diff --git a/src/config/schema.hints.ts b/src/config/schema.hints.ts index 63724c36e82..dd554e41bca 100644 --- a/src/config/schema.hints.ts +++ b/src/config/schema.hints.ts @@ -4,7 +4,6 @@ import { SENSITIVE_URL_HINT_TAG, } from "@openclaw/net-policy/redact-sensitive-url"; import { z } from "zod"; -import { createSubsystemLogger } from "../logging/subsystem.js"; import type { ConfigUiHints } from "../shared/config-ui-hints-types.js"; import { FIELD_HELP } from "./schema.help.js"; import { FIELD_LABELS } from "./schema.labels.js"; @@ -12,15 +11,6 @@ import { applyDerivedTags } from "./schema.tags.js"; import { isSensitiveConfigPath } from "./sensitive-paths.js"; import { sensitive } from "./zod-schema.sensitive.js"; -let log: ReturnType | null = null; - -function getLog(): ReturnType { - if (!log) { - log = createSubsystemLogger("config/schema"); - } - return log; -} - export type { ConfigUiHint, ConfigUiHints } from "../shared/config-ui-hints-types.js"; const GROUP_LABELS: Record = { @@ -289,8 +279,6 @@ function mapSensitivePathsMut(schema: z.ZodType, path: string, hints: ConfigUiHi if (isSensitive) { hints[path] = { ...hints[path], sensitive: true }; - } else if (isSensitiveConfigPath(path) && !hints[path]?.sensitive) { - getLog().debug(`possibly sensitive key found: (${path})`); } if (currentSchema instanceof z.ZodObject) { diff --git a/test/cli-json-stdout.e2e.test.ts b/test/cli-json-stdout.e2e.test.ts index 384f80f09f9..652c15c10f7 100644 --- a/test/cli-json-stdout.e2e.test.ts +++ b/test/cli-json-stdout.e2e.test.ts @@ -5,6 +5,28 @@ import path from "node:path"; import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; +function runSourceCli(tempHome: string, args: string[], envOverrides: NodeJS.ProcessEnv = {}) { + const env: NodeJS.ProcessEnv = { + ...process.env, + HOME: tempHome, + USERPROFILE: tempHome, + OPENCLAW_TEST_FAST: "1", + }; + delete env.OPENCLAW_HOME; + delete env.OPENCLAW_STATE_DIR; + delete env.OPENCLAW_CONFIG_PATH; + delete env.VITEST; + Object.assign(env, envOverrides); + + const entry = path.resolve(process.cwd(), "src/entry.ts"); + return spawnSync(process.execPath, ["--import", "tsx", entry, ...args], { + cwd: process.cwd(), + env, + encoding: "utf8", + maxBuffer: 10 * 1024 * 1024, + }); +} + describe("cli json stdout contract", () => { it("keeps `update status --json` stdout parseable even with legacy doctor preflight inputs", async () => { await withTempHome( @@ -13,23 +35,7 @@ describe("cli json stdout contract", () => { await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, "clawdbot.json"), "{}", "utf8"); - const env: NodeJS.ProcessEnv = { - ...process.env, - HOME: tempHome, - USERPROFILE: tempHome, - OPENCLAW_TEST_FAST: "1", - }; - delete env.OPENCLAW_HOME; - delete env.OPENCLAW_STATE_DIR; - delete env.OPENCLAW_CONFIG_PATH; - delete env.VITEST; - - const entry = path.resolve(process.cwd(), "src/entry.ts"); - const result = spawnSync( - process.execPath, - ["--import", "tsx", entry, "update", "status", "--json", "--timeout", "1"], - { cwd: process.cwd(), env, encoding: "utf8" }, - ); + const result = runSourceCli(tempHome, ["update", "status", "--json", "--timeout", "1"]); expect(result.status).toBe(0); const stdout = result.stdout.trim(); @@ -50,4 +56,44 @@ describe("cli json stdout contract", () => { { prefix: "openclaw-json-e2e-" }, ); }); + + it("keeps `config schema` stdout parseable at debug log level", async () => { + await withTempHome( + async (tempHome) => { + const result = runSourceCli(tempHome, ["config", "schema"], { + OPENCLAW_LOG_LEVEL: "debug", + }); + + expect(result.status).toBe(0); + const parsed = JSON.parse(result.stdout) as { + properties?: Record; + }; + expect(parsed.properties?.$schema).toEqual({ type: "string" }); + expect(result.stdout).not.toContain("possibly sensitive key found"); + expect(result.stderr).not.toContain("possibly sensitive key found"); + }, + { prefix: "openclaw-config-schema-json-e2e-" }, + ); + }); + + it("keeps `config validate --json` stdout parseable at debug log level", async () => { + await withTempHome( + async (tempHome) => { + const configPath = path.join(tempHome, "openclaw.json"); + await fs.writeFile(configPath, "{}", "utf8"); + const result = runSourceCli(tempHome, ["config", "validate", "--json"], { + OPENCLAW_CONFIG_PATH: configPath, + OPENCLAW_LOG_LEVEL: "debug", + }); + + expect(result.status).toBe(0); + expect(JSON.parse(result.stdout)).toMatchObject({ + valid: true, + path: configPath, + }); + expect(result.stdout).not.toContain("possibly sensitive key found"); + }, + { prefix: "openclaw-config-validate-json-e2e-" }, + ); + }); });