diff --git a/packages/coding-agent/src/cli/config-cli.ts b/packages/coding-agent/src/cli/config-cli.ts index a88917acc..e12d3002d 100644 --- a/packages/coding-agent/src/cli/config-cli.ts +++ b/packages/coding-agent/src/cli/config-cli.ts @@ -287,12 +287,14 @@ async function handleList(flags: { json?: boolean }): Promise { // // Redaction is driven by the value, not by classification alone. Marking an // unset credential as redacted would report every fresh install as having - // one configured, which leaks the opposite of what redaction is for. + // one configured, which leaks the opposite of what redaction is for. The + // settings panel persists "" when a credential is cleared and renders that + // as unset; the same semantics apply here (credentials are all strings). const result: Record = {}; for (const def of defs) { const value = settings.get(def.path); result[def.path] = - isCredential(def.path) && value !== undefined + isCredential(def.path) && value ? { redacted: true, type: def.type, description: def.description } : { value, type: def.type, description: def.description }; } @@ -321,10 +323,11 @@ async function handleList(flags: { json?: boolean }): Promise { for (const def of groups[group]) { // `list` dumps every value without anyone asking for a specific // credential, so redact here. `get ` stays an explicit - // single-value request and is left alone. An unset credential keeps its - // ordinary empty rendering: masking it would imply one is configured. + // single-value request and is left alone. An unset or cleared ("") + // credential keeps its ordinary rendering: masking it would imply one + // is configured. const value = settings.get(def.path); - const valueStr = isCredential(def.path) && value !== undefined ? REDACTED : formatValue(value); + const valueStr = isCredential(def.path) && value ? REDACTED : formatValue(value); const typeStr = getTypeDisplay(def); console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`); } diff --git a/packages/coding-agent/test/config-cli-credentials.test.ts b/packages/coding-agent/test/config-cli-credentials.test.ts index 5b877ab89..43269078b 100644 --- a/packages/coding-agent/test/config-cli-credentials.test.ts +++ b/packages/coding-agent/test/config-cli-credentials.test.ts @@ -152,6 +152,17 @@ describe("config list output", () => { expect(parsed["searxng.token"]).not.toHaveProperty("redacted"); }); + it("does not report a cleared credential as configured", async () => { + // The settings panel persists "" when a credential is cleared and renders + // that as unset; `config list` must agree, or a cleared token looks set. + await runConfigCommand({ action: "set", key: "searxng.token", value: SECRET, flags: { json: true } }); + await runConfigCommand({ action: "set", key: "searxng.token", value: "", flags: { json: true } }); + const output = await humanList(); + expect(output).not.toContain("searxng.token = ********"); + const { parsed } = await jsonList(); + expect(parsed["searxng.token"]).not.toHaveProperty("redacted"); + }); + it("leaves the Hindsight server URL readable", async () => { // It sits beside the API token under the same display condition, and is an // ordinary endpoint: masking it hides a value users need to inspect.