Skip to content

refactor(cli): route config command through daemon IPC#30262

Merged
noanflaherty merged 4 commits into
mainfrom
credence/config-cli-to-ipc
May 11, 2026
Merged

refactor(cli): route config command through daemon IPC#30262
noanflaherty merged 4 commits into
mainfrom
credence/config-cli-to-ipc

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

@credence-the-bot credence-the-bot Bot commented May 11, 2026

Why

config was the last general-purpose CLI command still tagged transport: "local", reading and writing config.json straight from the CLI process. That bypassed the daemon's in-memory config cache, ConfigWatcher suppression, embedding cache invalidation, and provider re-init - so assistant config set llm.default.model gpt-5 would touch the file on disk but leave the running daemon stale until a restart or an unrelated PATCH happened to flush the cache.

Per offline conversation, the call is: every general-purpose CLI command should be a thin IPC wrapper. This PR closes the gap.

What changed

CLI (cli/commands/config.ts):

  • Re-tagged transport: "ipc". Dropped direct loadRawConfig / saveRawConfig / AssistantConfigSchema / getSchemaAtPath imports.
  • set <key> <value> now builds a single-key deep-merge patch from the dotted path and sends it through cliIpcCall("config_patch", { body: patch }). The daemon's existing handlePatchConfig already deep-merges, suppresses the watcher, invalidates the embedding cache, and reinits providers.
  • get <key> and list fetch the full raw config via cliIpcCall("config_get") and walk the dotted path / flatten in the CLI process. Pure reads.
  • schema [path] calls a new config_schema_get IPC route (see below).
  • validate-allowlist is unchanged - uses a dynamic require() to load security/secret-allowlist.js, which has no daemon globals and only reads secret-allowlist.json from disk. I added a TODO for a future config_allowlist_validate daemon route so this subcommand can become a pure thin wrapper too.

Helper bridge (cli/lib/nested-value.ts, new):
Thin re-export of getNestedValue / setNestedValue from config/loader.js. These two helpers are pure and dependency-free, but the cli/no-daemon-internals ESLint rule forbids ipc-tagged commands from importing config/loader.js directly (because other exports from that module pull in daemon-only globals). Helper modules with no registerCommand call are exempt from the rule, so the canonical pattern is to bridge through cli/lib/.

Route (runtime/routes/conversation-query-routes.ts):

  • New handleGetConfigSchema({ queryParams }) handler. Pure function: calls z.toJSONSchema(AssistantConfigSchema, { unrepresentable: "any", io: "input" }) for the full schema, or getSchemaAtPath + toJSONSchema for the scoped case. No daemon state touched.
  • New route definition: operationId: "config_schema_get", GET config/schema, optional ?path= query param.

Policy (runtime/auth/route-policy.ts):

  • New entry: config/schema:GET → settings.read. Mirrors the existing config:GET actor scope.

CLI surface after this PR

After this PR + #30261 (autonomy delete) lands, the only non-IPC CLI commands are the ones that genuinely shouldn't be IPC:

  • transport: "local": keys (hybrid - uses the daemon-credential-client side-channel), completions (shell scripts), credential-execution (CES RPC, different transport)
  • transport: "bootstrap": default-action (interactive entry, starts the daemon)

Test plan

  • bun run typecheck
  • bun run lint ✅ (the helper-extraction pattern in cli/lib/nested-value.ts clears the four would-be forbidden-import errors on the new IPC command)
  • Daemon path is exercised by existing route tests (config_get, config_patch). The new config_schema_get is a pure function over AssistantConfigSchema with no state - failure modes are the same as the inline path it replaces.

Pre-merge manual smoke I'd suggest running:

$ assistant config get llm.default.provider
$ assistant config set calls.enabled true
$ assistant config list --search calls
$ assistant config schema calls
$ assistant config validate-allowlist

Each of those should round-trip through the running daemon now.


Open in Devin Review

The `config` CLI was the last general-purpose command still tagged
`transport: "local"`, reading and writing `config.json` directly from the
CLI process. That bypassed the daemon's in-memory config cache, file
watcher, embedding cache invalidation, and provider re-init - so every
`assistant config set ...` left the running daemon stale until a restart
or unrelated PATCH happened to flush the cache.

This change wires every subcommand through the existing daemon IPC
surface so the daemon stays coherent on every write and is the single
source of truth on every read.

What changed
- `cli/commands/config.ts`: re-tagged `transport: "ipc"`, dropped direct
  `loadRawConfig` / `saveRawConfig` / `AssistantConfigSchema` /
  `getSchemaAtPath` imports.
  - `set`: builds a single-key deep-merge patch from the dotted path and
    sends it to `config_patch`. Daemon does the merge + cache/provider
    reinit (already implemented in `handlePatchConfig`).
  - `get` / `list`: fetch the full raw config via `config_get` and walk
    the dotted path in the CLI process. Pure read.
  - `schema`: calls the new `config_schema_get` IPC route (see below).
  - `validate-allowlist`: kept as-is - uses a dynamic `require()` to
    load `security/secret-allowlist.js`, which has no daemon-globals
    and only reads `secret-allowlist.json` from disk. Marked with a
    TODO to add a `config_allowlist_validate` daemon route later so
    the subcommand can become a pure thin wrapper too.
- `cli/lib/nested-value.ts` (new): thin re-export of
  `getNestedValue`/`setNestedValue` from `config/loader.js`. These two
  helpers are pure and dependency-free, but `cli/no-daemon-internals`
  forbids `ipc` commands from importing `config/loader.js` directly
  (it pulls in daemon-only globals via other exports). Helper modules
  with no `registerCommand` call are exempt from the rule, so this is
  the canonical bridge.
- `runtime/routes/conversation-query-routes.ts`: new
  `handleGetConfigSchema` handler + `config_schema_get` route
  (`GET config/schema?path=...`). Pure read; calls
  `z.toJSONSchema(AssistantConfigSchema)` or `getSchemaAtPath` for the
  scoped case, returns `{ schema }`. No daemon state touched.
- `runtime/auth/route-policy.ts`: new actor-endpoint entry
  `config/schema:GET → settings.read`.

After this change the only commands still tagged non-`ipc` are:
- `transport: "local"`: `keys` (hybrid - uses daemon-credential-client
  side-channel), `completions` (shell scripts), `credential-execution`
  (CES RPC, different transport)
- `transport: "bootstrap"`: `default-action` (interactive entry, starts
  the daemon)

`autonomy` is being removed in #30261.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f45a0f68f2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread assistant/src/cli/commands/config.ts Outdated
const patch: Record<string, unknown> = {};
setNestedValue(patch, key, parsed);

const result = await cliIpcCall("config_patch", { body: patch });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve config set replacement semantics for object/null

Routing config set through config_patch changes behavior for JSON object and null values because the daemon patch path uses deepMergeOverwrite (in config/loader.ts) rather than direct assignment. For example, assistant config set llm {} now leaves existing llm.* keys intact (merge) instead of replacing llm, and assistant config set some.newKey null can become a no-op when the key is absent. This is a regression from the previous setNestedValue(raw, key, parsed) behavior and makes the command unable to reliably set exact values at a dotted path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 8 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 validate-allowlist subcommand uses local file I/O under ipc-tagged command

The validate-allowlist subcommand at assistant/src/cli/commands/config.ts:273-299 uses a dynamic require("../../security/secret-allowlist.js") to read files directly from disk, while the parent config command is now tagged as transport: "ipc". The AGENTS.md rule says ipc-tagged commands should "never import daemon-internal modules." The dynamic require likely evades the cli/no-daemon-internals ESLint rule at lint time, so this won't cause a build failure. At runtime the transport tag is inert (it's just metadata), so the subcommand still works. However, this creates a semantic mismatch: the other subcommands need a running assistant (IPC), but validate-allowlist works offline. The TODO comment in the help text acknowledges this gap. Consider either (a) splitting validate-allowlist into a separate local-tagged command, or (b) adding the planned IPC route.

(Refers to lines 273-299)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +60 to +61
Configuration is managed by the assistant daemon. The CLI sends every
read/write through the daemon's IPC channel so the in-memory cache,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text uses "daemon" instead of "assistant" (top-level config)

The AGENTS.md rule under User-Facing Terminology states: "In all user-facing text — CLI output, error messages, help strings… use 'assistant' instead of 'daemon'." The top-level config help text (shown via --help) says "assistant daemon" and "daemon's IPC channel" on lines 60–61, violating this mandatory rule.

Suggested change
Configuration is managed by the assistant daemon. The CLI sends every
read/write through the daemon's IPC channel so the in-memory cache,
Configuration is managed by the assistant. The CLI sends every
read/write through an IPC channel so the in-memory cache,
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +95 to +97
The CLI sends the change to the daemon, which deep-merges it into
config.json, invalidates caches, and reinitializes providers so the new
value takes effect immediately.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text uses "daemon" instead of "assistant" (set subcommand)

The config set subcommand's addHelpText block references "the daemon" on line 95. Per AGENTS.md, user-facing help strings must use "assistant" instead of "daemon".

Suggested change
The CLI sends the change to the daemon, which deep-merges it into
config.json, invalidates caches, and reinitializes providers so the new
value takes effect immediately.
The CLI sends the change to the assistant, which deep-merges it into
config.json, invalidates caches, and reinitializes providers so the new
value takes effect immediately.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +144 to +146
Fetches the full config from the daemon and prints the value at the
given key path. If the key is not set, prints "(not set)". Object
values are pretty-printed as indented JSON.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text uses "daemon" instead of "assistant" (get subcommand)

The config get subcommand's addHelpText block references "the daemon" on line 144. Per AGENTS.md, user-facing help strings must use "assistant" instead of "daemon".

Suggested change
Fetches the full config from the daemon and prints the value at the
given key path. If the key is not set, prints "(not set)". Object
values are pretty-printed as indented JSON.
Fetches the full config from the assistant and prints the value at the
given key path. If the key is not set, prints "(not set)". Object
values are pretty-printed as indented JSON.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +178 to +180
Asks the daemon for the JSON Schema of the entire config object, or
the sub-schema at the given path. Useful for understanding available
fields, their types, defaults, and constraints.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text uses "daemon" instead of "assistant" (schema subcommand)

The config schema subcommand's addHelpText block references "the daemon" on line 178. Per AGENTS.md, user-facing help strings must use "assistant" instead of "daemon".

Suggested change
Asks the daemon for the JSON Schema of the entire config object, or
the sub-schema at the given path. Useful for understanding available
fields, their types, defaults, and constraints.
Asks the assistant for the JSON Schema of the entire config object, or
the sub-schema at the given path. Useful for understanding available
fields, their types, defaults, and constraints.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +209 to +211
Fetches the full raw configuration from the daemon and prints it as
pretty-printed JSON. If no configuration has been set, prints
"No configuration set".
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text uses "daemon" instead of "assistant" (list subcommand)

The config list subcommand's addHelpText block references "the daemon" on line 209. Per AGENTS.md, user-facing help strings must use "assistant" instead of "daemon".

Suggested change
Fetches the full raw configuration from the daemon and prints it as
pretty-printed JSON. If no configuration has been set, prints
"No configuration set".
Fetches the full raw configuration from the assistant and prints it as
pretty-printed JSON. If no configuration has been set, prints
"No configuration set".
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +266 to +268
This subcommand reads the allowlist file directly from disk via a dynamic
require — no daemon route exists yet. (TODO: add config_allowlist_validate
IPC route so this command can be a pure thin wrapper too.)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 User-facing help text exposes internal implementation details and uses "daemon"

The config validate-allowlist subcommand's addHelpText block on lines 266–268 says "This subcommand reads the allowlist file directly from disk via a dynamic require — no daemon route exists yet." This violates two AGENTS.md rules: (1) user-facing text must say "assistant" not "daemon", and (2) internal implementation details ("dynamic require", "IPC route", TODO notes) should not be exposed in --help output that users and AI agents read.

Suggested change
This subcommand reads the allowlist file directly from disk via a dynamic
require no daemon route exists yet. (TODO: add config_allowlist_validate
IPC route so this command can be a pure thin wrapper too.)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/cli/lib/nested-value.ts Outdated
* Helper modules (no `registerCommand` call) are exempt from the rule,
* so the daemon-internal import below is allowed here.
*/
export { getNestedValue, setNestedValue } from "../../config/loader.js";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 nested-value.ts re-export pulls in full config/loader.ts module at import time

The re-export at assistant/src/cli/lib/nested-value.ts:13 imports from ../../config/loader.ts, which has module-level side effects: it imports logger, platform utilities, feature flags, and initializes module-level variables (cached, loading, etc.). While getNestedValue and setNestedValue themselves are pure, importing them causes the entire config/loader.ts module to be evaluated. For ipc-tagged CLI commands, this means daemon-internal modules are loaded into the CLI process even though they're not used. This doesn't cause a runtime error (the side effects are benign initialization), but it increases CLI startup time and memory usage. A cleaner approach would be to extract these two pure functions into their own module.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@credence-the-bot
Copy link
Copy Markdown
Contributor Author

OpenAPI Spec Check failure fixed: regenerated openapi.yaml to include the new config_schema_get route (GET /v1/config/schema). Commit ce78802, +19 lines (single route entry).

@codex review
@devin review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce78802785

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread assistant/src/cli/commands/config.ts Outdated
Comment on lines +121 to +125
// Build a single-key deep-merge patch object from the dotted path.
const patch: Record<string, unknown> = {};
setNestedValue(patch, key, parsed);

const result = await cliIpcCall("config_patch", { body: patch });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve null assignments for newly introduced config paths

config set now always forwards a one-key patch through config_patch, which uses deepMergeOverwrite semantics; when the target subtree/key does not yet exist, null leaves are stripped instead of persisted. This regresses prior CLI behavior: commands like assistant config set heartbeat.activeHoursStart null (or any nullable key under an unset parent) silently no-op and the daemon falls back to schema defaults instead of storing the explicit null override. That breaks disabling nullable settings on fresh configs unless users know a multi-step workaround.

Useful? React with 👍 / 👎.

…view

Three issues caught in #30262 review, fixed together:

1. **Functional regression (Codex P1).** Routing `config set` through
   `config_patch` went through `deepMergeOverwrite`, which strips `null`
   leaves under newly-introduced subtrees and merges object subtrees
   rather than replacing them. So `config set heartbeat.activeHoursStart
   null` lost the null and `config set llm.profiles {}` no-op'd instead
   of clearing. Add a separate `config_set` IPC route that calls
   `setNestedValue` directly. Direct replacement, null preserved.

2. **User-facing text (Devin).** AGENTS.md mandates "assistant" not
   "daemon" in help/error text shown to users. Rewrote the addHelpText
   blocks accordingly. Internal docstrings/comments still say "daemon"
   (matches the rest of the codebase's internal terminology).

3. **validate-allowlist under ipc tag (Devin).** Was doing a dynamic
   `require("../../security/secret-allowlist.js")` from an IPC-tagged
   command. Move it behind a new `config_allowlist_validate` route so
   the CLI no longer reaches into daemon-internal modules.

Implementation notes:

- New routes `config_set` (POST /v1/config/set) and
  `config_allowlist_validate` (GET /v1/config/allowlist/validate), both
  in `conversation-query-routes.ts`. Both write paths share a new
  `commitConfigWrite` helper (watcher suppress + saveRawConfig +
  cache invalidation + provider re-init + fingerprint).
- Route-policy entries added: `config/set:POST → settings.write`,
  `config/allowlist/validate:GET → settings.read`.
- `cli/lib/nested-value.ts` is now an inlined duplicate of the loader
  helpers (not a bridge re-export). Documented in the file header. The
  CLI must not import `config/loader.js` because loader has module-load
  side effects (cache + watcher) that shouldn't run in the CLI process.
- Tests rewritten to mock `ipc/cli-client.js` instead of
  `config/loader.js`. The platform-guard test now asserts on IPC call
  shape; the schema CLI test verifies that a daemon 400 maps to exit
  code 2 via `exitFromIpcResult`.
- OpenAPI regenerated for the two new routes.
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

Pushed 7e526a11d8 addressing review feedback:

  1. Codex P1 (null preservation regression) — Added a dedicated config_set IPC route that uses setNestedValue directly instead of going through deepMergeOverwrite. config set heartbeat.activeHoursStart null now preserves the null, and config set llm.profiles {} now clears instead of merging.

  2. Devin user-facing text — Rewrote the addHelpText blocks to say "assistant" not "daemon". Internal docstrings/comments still use "daemon" since that matches the rest of the codebase's terminology.

  3. Devin validate-allowlist — New config_allowlist_validate IPC route. The CLI no longer reaches into security/secret-allowlist.js directly.

Test rewrites:

  • config-set-platform-guard.test.ts now mocks cli-client.js and asserts on IPC call shape.
  • config-schema-cmd.test.ts keeps the pure getSchemaAtPath / z.toJSONSchema unit tests; the CLI test now verifies a daemon 400 maps to exit code 2 via exitFromIpcResult.

OpenAPI regenerated for the two new routes. Lint + typecheck clean.

@codex review
@devin review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e526a11d8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +562 to +564
const { path, value } = body as { path?: unknown; value?: unknown };
if (typeof path !== "string" || path.length === 0) {
throw new BadRequestError("`path` must be a non-empty string");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject config_set requests missing value

handleSetConfig only validates path, so a request like POST /v1/config/set with { "path": "foo" } is accepted and passes value === undefined into setNestedValue. When this is persisted, JSON.stringify drops undefined, which effectively removes the key instead of returning a 400. Because this route is exposed over HTTP/IPC (not just the CLI), malformed callers can silently delete config keys; require that the value field is present (while still allowing explicit null).

Useful? React with 👍 / 👎.

Comment on lines +587 to +589
function handleValidateAllowlist() {
const errors = validateAllowlistFile();
if (errors == null) return { exists: false } as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Catch allowlist parse errors in config_allowlist_validate

handleValidateAllowlist calls validateAllowlistFile() directly, but that helper does a raw JSON.parse and can throw on malformed secret-allowlist.json. Before this change, the CLI wrapped this path in try/catch and returned a user-readable validation failure; now the same input bubbles up as a daemon 500/IPC error path. Wrapping this route in a local try/catch preserves predictable CLI behavior for malformed files.

Useful? React with 👍 / 👎.

Two findings from Codex review 4260902788 on 7e526a1:

**P2 — `config_set` accepts missing `value` field**

`handleSetConfig` validated `path` but not the presence of `value`. A
request body like `{ "path": "foo" }` (no `value` key) passed `undefined`
into `setNestedValue`, which then got dropped by `JSON.stringify` at save
time — silently deleting the key. That is distinct from the documented
"set to null to clear" semantics and made the route a footgun.

Add an explicit `"value" in bodyRecord` check that throws a
`BadRequestError("`value` is required (use `null` to clear a key)")`.
Explicit `null` is still accepted (the existing null-preservation path
in the writer handles it).

**P3 — `config_allowlist_validate` doesn't catch JSON.parse errors**

`validateAllowlistFile()` does a raw `JSON.parse` on
`secret-allowlist.json` and can throw on malformed JSON. With the route
calling it directly, a parse error propagated as a 500 — losing the
pre-IPC behavior of printing a user-readable message and exiting 1.

Wrap the handler in try/catch and return
`{ exists: true, parseError: message, errors: [] }` on failure. The CLI
side now renders `parseError` as
`Failed to read secret-allowlist.json: <msg>` and exits 1, matching the
pre-refactor surface.

**Tests**

New `assistant/src/__tests__/config-set-route.test.ts` covering both
routes daemon-side (9 tests, all pass):

- `config_set`: rejects missing `value`, accepts explicit null, rejects
  missing/empty `path`, rejects non-object body, writes scalar through
  to raw config.
- `config_allowlist_validate`: returns `parseError` when underlying
  validator throws, returns `{ exists: false }` when file is absent,
  returns `{ exists: true, errors }` (empty or with pattern details)
  for the well-formed cases.

No public API change beyond the new `parseError` field on the validate
response, which the CLI is the only consumer of.
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@codex review

Round 2 — addressed both findings from review 4260902788:

P2 (config_set missing value) → added explicit "value" in body check in handleSetConfig that throws BadRequestError("valueis required (usenull to clear a key)"). Explicit null is still accepted.

P3 (config_allowlist_validate doesn't catch JSON.parse errors) → wrapped handleValidateAllowlist in try/catch returning { exists: true, parseError: message, errors: [] }. CLI renders it as Failed to read secret-allowlist.json: <msg> and exits 1 (matches pre-IPC behavior).

Added daemon-side tests in assistant/src/__tests__/config-set-route.test.ts (9 cases, all pass) covering both routes.

Head: 1c9069db9b.

@noanflaherty noanflaherty merged commit f644f31 into main May 11, 2026
13 checks passed
@noanflaherty noanflaherty deleted the credence/config-cli-to-ipc branch May 11, 2026 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant