- docs/concepts/model-providers.md: add proxy-route shaping rule for the
09ec5d2c4d fix that suppresses implicit Anthropic beta headers
(`claude-code-20250219`, `interleaved-thinking-2025-05-14`, OAuth markers)
on non-direct endpoints, parallel to the existing OpenAI
`compat.supportsDeveloperRole` rule.
- docs/gateway/cli-backends.md: add a "Fallback prelude from claude-cli
sessions" section for a96f1fa5ef so users know that non-CLI fallback
candidates after a claude-cli failure are now seeded with a context prelude
harvested from Claude Code's `~/.claude/projects/` JSONL (preferring the
latest `/compact` summary, coalescing tool blocks, skipping same-provider
`--resume` fallbacks).
The required-typed param introduced in 9987e7797f broke
attempt-execution.cli.test.ts and auth-profile-runtime-contract.test.ts
which construct runAgentAttempt params without an originalProvider field.
Make it optional and explicitly require the typeof check before passing
to isClaudeCliProvider so a missing field correctly skips the seed
(defensive default for fallback paths that didn't plumb the original
provider through, no-op for non-fallback paths).
Addresses review on #72069:
- Codex P1 ("Gate Claude prelude seeding by source provider"): the
guard checked the *current* fallback candidate but not the failed
attempt. A session that still carried a stale
cliSessionBindings["claude-cli"] from an unrelated past run would
inject Claude transcript context into a fallback chain that started
on a different provider (e.g. openai -> openai-codex), leaking
irrelevant prior conversation. Plumb `originalProvider` (the
user-requested provider for the chain) through to runAgentAttempt
and require `isClaudeCliProvider(originalProvider)` before reading
Claude history.
- Codex P2 ("Prefer latest compact boundary when summary is missing"):
the resolver always preferred the most recent explicit summary, so
a later compaction without its own summary entry (rare crash case)
paired stale summary text with post-latest-boundary turns. Restructure
readClaudeCliFallbackSeed to queue summaries into pendingSummary and
flush each boundary's pair atomically. A boundary with no preceding
summary now correctly falls back to the boundary's own content
rather than serving an older summary alongside fresh turns.
- Greptile P2 (newest-first break vs sparse coverage): the
formatFallbackTurns walk intentionally stops on the first oversized
turn so the prelude stays a contiguous "what was happening just
before the failure" window. Document the design choice inline so a
future maintainer doesn't reflexively change it to skip-and-continue.
Tests:
- New gateway cases for the boundary-without-summary edge case and
for trailing summaries written without a paired boundary.
- existing 33 attempt-execution + 14 cli-session-history tests still
pass; broader src/agents/command suite stays green (63/63).
Local default oxlint did not run --type-aware so the warning was missed
on the initial commit; CI surfaced it via check-lint. Hoist the heading
into a named const so its length is read directly without the assertion.
When a claude-cli attempt failed with a fallbackable error (e.g. a 402
billing limit), the next candidate -- typically a non-CLI provider --
ran with no prior conversation context. Claude Code keeps its own
JSONL session under ~/.claude/projects/, but the fallback runner only
sees what OpenClaw assembles from its own transcript, which is empty
for claude-cli sessions. The fallback model therefore behaved as if
the conversation just started, even though Claude later resumed fine.
Resolution mirrors what Claude Code itself does on resume after
compaction: prefer the explicit `/compact` summary, then append the
most recent post-boundary turns up to a char budget. Concretely:
- `readClaudeCliFallbackSeed` (gateway): walks the Claude JSONL with
awareness of `type: "summary"` and `type: "system",
subtype: "compact_boundary"` entries. Pre-boundary turns are dropped
(they are represented by the summary); post-boundary turns become
the recent-window. Multiple compactions are handled by preferring
the latest summary. Path safety reuses the existing
`resolveClaudeCliSessionFilePath` validation.
- `formatClaudeCliFallbackPrelude` / `buildClaudeCliFallbackContext\
Prelude` (agents helpers): format the harvested seed into a labeled
prelude. Tool blocks are coalesced to compact "(tool call: name)" /
"(tool result: …)" hints to keep the prompt budget honest. Newest
turns are kept first when truncating; the summary is clearly
labeled "(truncated)" if it overflows.
- `resolveFallbackRetryPrompt`: gains an optional
`priorContextPrelude` that prepends before the existing retry
marker. Empty/whitespace preludes are ignored; first-attempt prompts
are unchanged.
- `runAgentAttempt`: builds the prelude when `isFallbackRetry === true`
AND the new candidate is non-claude-cli AND a Claude-cli session
binding is present. Same-provider fallbacks (claude-cli to
claude-cli) are unaffected because Claude's own --resume still works.
Verified the new tests (12 in cli-session-history, 12 added to
attempt-execution) catch the regression: removing the prelude prepend
in resolveFallbackRetryPrompt makes both new prelude cases fail,
restoring the original cold-start behavior.
References:
- https://code.claude.com/docs/en/how-claude-code-works
- "Inside Claude Code: The Session File Format"
https://databunny.medium.com/inside-claude-code-the-session-file-format-and-how-to-inspect-it-b9998e66d56b