fix(runtime): the kimi fallback hop spawned a binary that 401s

A throttled subscription had nowhere to go. `claude_cli.default` carried no
`fallback`, and neither target alias was declared — they existed only as
commented-out examples. Forcing a 429 with a shimmed `claude` surfaced four
defects that all read as correct config and do nothing:

  - a `[providers.models.<f>.<a>.env]` SUB-TABLE is parsed then silently
    ignored ("fields must live directly under ..."). This block was already
    live for claude_cli.default, so the token injection has been inert. For
    the glm alias it would have dropped the z.ai routing AND the clearing of
    CLAUDE_CODE_OAUTH_TOKEN — credentials crossing between providers.
  - an empty `[providers.models.kimi_cli.default]` is skipped at runtime.
  - a claude_cli alias used as a fallback needs a non-empty `api_key` to pass
    FamilyProviderFactory's default readiness gate, even though the provider
    ignores the key and authenticates through `env`. Absent it the agent dies
    at STARTUP, which takes out every mission, not just throttled ones.
  - timeout_secs=600 capped every turn under the 3600s TURN_TIMEOUT from
    4c418f7, so that raise bought long turns nothing.

The kimi hop then 401'd: `kimi_cli` spawns the `kimi` binary, which rejects a
Kimi Code key. Kimi is reached the way the agent-kimi microVMs already reach
it — the `claude` binary against api.kimi.com/coding (no /v1; Claude Code
appends it). So the hop is `claude_cli.kimi`, and `kimi_cli.default` is left
declared but out of the chain so the finding stays visible.

kimi-home joins SEEDED_PATHS: each claude_cli fallback alias needs its own
HOME with a .claude.json, and separate homes stop two concurrent fallbacks
from sharing one Claude Code session directory.

Measured on gw-04 against the live config, zeroclaw 0.8.4:
  throttled primary -> OK (chain fires)   happy path -> OK (no regression)
Isolating each hop: kimi alone answers, glm alone answers, and with an empty
fallback the turn fails `rate_limited` at phase=http_response — the control
that makes the other two mean something. An earlier OK came from glm, not
kimi, so the reply alone was never evidence.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-10 12:46:11 -07:00
co-authored by Claude Opus 5
parent 4c418f7d9b
commit 104e3ef27c
+6 -1
View File
@@ -374,9 +374,14 @@ const SEEDED_PATHS: &[&str] = &[
// token refresh or project state in one mission cannot leak into another. // token refresh or project state in one mission cannot leak into another.
".claude", ".claude",
".claude.json", ".claude.json",
// Per-CLI state for the alternate backends; small. // Per-CLI state for the alternate backends; small. `glm-home` and
// `kimi-home` are separate HOMEs for the two `claude_cli` fallback
// aliases: each needs its own `.claude.json` so the binary skips
// onboarding, and keeping them apart stops two concurrent fallbacks from
// sharing one Claude Code session directory.
".kimi-code", ".kimi-code",
"glm-home", "glm-home",
"kimi-home",
// The seeded agent library. // The seeded agent library.
"agents", "agents",
]; ];