fix(cli): keep config schema stdout parseable (#110496)

This commit is contained in:
Peter Steinberger
2026-07-18 08:02:04 +01:00
committed by GitHub
parent 1f160696dc
commit 13ae897cfb
4 changed files with 70 additions and 30 deletions
+1 -1
View File
@@ -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"],
+6
View File
@@ -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",
-12
View File
@@ -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<typeof createSubsystemLogger> | null = null;
function getLog(): ReturnType<typeof createSubsystemLogger> {
if (!log) {
log = createSubsystemLogger("config/schema");
}
return log;
}
export type { ConfigUiHint, ConfigUiHints } from "../shared/config-ui-hints-types.js";
const GROUP_LABELS: Record<string, string> = {
@@ -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) {
+63 -17
View File
@@ -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<string, unknown>;
};
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-" },
);
});
});