Skip to content

fix(config): accept partial cliBackends overrides by making command optional#5

Merged
LightDriverCS merged 1 commit into
mainfrom
fix/cli-backend-partial-override
Apr 20, 2026
Merged

fix(config): accept partial cliBackends overrides by making command optional#5
LightDriverCS merged 1 commit into
mainfrom
fix/cli-backend-partial-override

Conversation

@LightDriverCS

Copy link
Copy Markdown

Summary

  • Make agents.defaults.cliBackends.<backendId>.command optional at both the zod schema level (CliBackendSchema) and the TS-type level (CliBackendConfig).
  • Introduce ResolvedCliBackendConfig with command: string so downstream consumers don't lose the non-null guarantee after resolveCliBackendConfig validates it.
  • Narrow in one place (cli-runner/execute.ts) at the spawn site instead of propagating the stricter type up through the whole config plumbing.

Why

Users cannot currently write a partial cliBackends override — e.g. pinning claude-opus-4-7 past the shared family-alias table in extensions/anthropic/cli-shared.ts:

{
  "agents": {
    "defaults": {
      "cliBackends": {
        "claude-cli": {
          "modelAliases": { "claude-opus-4-7": "claude-opus-4-7" }
        }
      }
    }
  }
}

This fails validation with command: expected string, received undefined, forcing users to duplicate the entire backend block (every arg, every clearEnv entry, etc.) just to override an alias. The runtime merge (mergeBackendConfig at src/agents/cli-backends.ts:130-142) already handles partials correctly via object spread, and resolveCliBackendConfig guards on config.command?.trim() returning null when unresolvable — so the validation constraint is out of sync with the actual runtime contract.

Every other field on CliBackendSchema is already optional; this change aligns command with the rest.

Why this shape was picked

Two alternatives were weighed:

  • Make CliBackendConfig.command required and introduce a separate PartialCliBackendConfig input type. Cleaner on paper but requires a wide refactor: every caller currently takes the same type for both input and resolved backends. Lots of churn for the same behavior.
  • Assertion at the spawn site. What this PR does. One narrowed type (ResolvedCliBackendConfig) exported for anyone who wants the tightened contract, and a single runtime guard in execute.ts that throws with a clear message if the invariant is ever violated. No changes to existing callers; no new optional chaining anywhere else.

Test plan

  • pnpm exec vitest run src/agents/cli-backends.test.ts → 19/19 passing (new regression test + existing 18)
  • pnpm exec vitest run src/config/ src/agents/ → exit 0
  • pnpm tsgo → clean
  • pnpm check:import-cycles / pnpm check:madge-import-cycles → 0 cycles
  • New test: partial modelAliases-only override parses, resolves non-null, inherits base command, and surfaces the user's alias entry

Context

Hit while switching Aurelius (one of Bench's OpenClaw-hosted agents) from Opus 4.6 → Opus 4.7. The CLI's opus alias on v2.1.87 still resolves to 4.6, so the only way to force 4.7 through OpenClaw today is either a bundle patch on cli-shared-*.js (brittle — file hashes change on every publish) or a partial modelAliases config override (blocked by this validation bug). This PR unblocks the latter.

…ptional

`agents.defaults.cliBackends.<backendId>` rejects partial overrides today:
supplying only `{ modelAliases: { ... } }` fails schema validation with
"command: expected string, received undefined", even though the runtime
merge (`mergeBackendConfig`) already composes with base `command` via
object spread and `resolveCliBackendConfig` guards on
`config.command?.trim()` returning null when unresolvable.

Make `command` optional at the schema and TS-type level so config-only
alias remaps (e.g. pinning `claude-opus-4-7` past the shared family-alias
table in `cli-shared.ts`) no longer require duplicating the full backend
block. Every other field on the schema is already optional; this aligns
`command` with the rest and with the runtime's actual contract.

Changes:
- `CliBackendSchema.command` → `.optional()` in zod-schema.core.ts
- `CliBackendConfig.command` → optional in types.agent-defaults.ts
- New `ResolvedCliBackendConfig` type narrows `command: string` so
  downstream consumers don't lose the non-null guarantee
- `cli-runner/execute.ts` guards once at the spawn site and reuses the
  narrowed `backendCommand` for argv[0] and the argv log line
- Regression test: partial `modelAliases`-only override parses, resolves
  non-null, and surfaces the user's alias entry
@LightDriverCS
LightDriverCS merged commit c5ccef4 into main Apr 20, 2026
1 check passed
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