What happened?
Resuming a session restores the wrong model when the provider's response echoes a model name that differs from the id pi routed to.
getSessionContextSettings (packages/coding-agent/src/core/session-manager.ts:366-374) lets every assistant message overwrite model, so the last assistant message beats the last model_change. For anthropic-messages, message.model is whatever the upstream put in message_start (packages/ai/src/api/anthropic-messages.ts:598), not the catalog id. Behind a relay that namespaces its ids (catalog anthropic/claude-opus-5, forwarded upstream as claude-opus-5, Anthropic echoes claude-opus-5, relay passes it through) the session file ends up as
{"type":"model_change","provider":"relay","modelId":"anthropic/claude-opus-5"}
{"type":"message","message":{"role":"assistant","provider":"relay","model":"claude-opus-5", ...}}
and pi -c looks up the bare id, misses, and switches models:
Warning: Could not restore model relay/claude-opus-5. Using relay/anthropic/claude-opus-5
Only interactive mode shows that warning; -p and RPC fall back silently. I hit it on my own relay (pengepul) after it prefixed its ids to tell two upstreams apart that both serve a bare claude-opus-5. The relay could rewrite the echo too, but pi already records its own routing decision in model_change, and #9188 shows other proxies (new-api, one-api) echo the same way. Same root cause as #9188, different symptom: pinning message.model as proposed there fixes sessions written from then on, while files that already carry the echoed name keep restoring wrong. So I think the read side should stop treating message.model as the routing record either way.
Steps to reproduce
models.json: provider relay, api: anthropic-messages, one model anthropic/claude-opus-5, baseUrl pointing at a stub that serves a normal SSE stream but sets message_start.message.model to claude-opus-5 (30-line node script, can attach).
pi -ne -p "say ok"
pi -ne -c prints the warning above. Validated with -ne; no extension involved.
Expected behavior
Restore uses the last model_change in the path and only falls back to the assistant message when the path has none (files older than 98c85bf, or embedders that never append one). As far as I can tell the CLI always writes one at session start (sdk.ts:381-384), so that covers it, but if there is a first-turn path without a model_change I would like to know before writing the fix. The alternative is the write side: keep model as the requested id and put the echo in responseModel, as openai-completions does. I can do either as a PR once approved. build-context.test.ts currently asserts the opposite precedence and would flip.
Version
0.85.1 (still present on main at 9767ba2)
What happened?
Resuming a session restores the wrong model when the provider's response echoes a model name that differs from the id pi routed to.
getSessionContextSettings(packages/coding-agent/src/core/session-manager.ts:366-374) lets every assistant message overwritemodel, so the last assistant message beats the lastmodel_change. Foranthropic-messages,message.modelis whatever the upstream put inmessage_start(packages/ai/src/api/anthropic-messages.ts:598), not the catalog id. Behind a relay that namespaces its ids (cataloganthropic/claude-opus-5, forwarded upstream asclaude-opus-5, Anthropic echoesclaude-opus-5, relay passes it through) the session file ends up asand
pi -clooks up the bare id, misses, and switches models:Only interactive mode shows that warning;
-pand RPC fall back silently. I hit it on my own relay (pengepul) after it prefixed its ids to tell two upstreams apart that both serve a bareclaude-opus-5. The relay could rewrite the echo too, but pi already records its own routing decision inmodel_change, and #9188 shows other proxies (new-api, one-api) echo the same way. Same root cause as #9188, different symptom: pinningmessage.modelas proposed there fixes sessions written from then on, while files that already carry the echoed name keep restoring wrong. So I think the read side should stop treatingmessage.modelas the routing record either way.Steps to reproduce
models.json: providerrelay,api: anthropic-messages, one modelanthropic/claude-opus-5,baseUrlpointing at a stub that serves a normal SSE stream but setsmessage_start.message.modeltoclaude-opus-5(30-line node script, can attach).pi -ne -p "say ok"pi -ne -cprints the warning above. Validated with-ne; no extension involved.Expected behavior
Restore uses the last
model_changein the path and only falls back to the assistant message when the path has none (files older than 98c85bf, or embedders that never append one). As far as I can tell the CLI always writes one at session start (sdk.ts:381-384), so that covers it, but if there is a first-turn path without amodel_changeI would like to know before writing the fix. The alternative is the write side: keepmodelas the requested id and put the echo inresponseModel, asopenai-completionsdoes. I can do either as a PR once approved.build-context.test.tscurrently asserts the opposite precedence and would flip.Version
0.85.1 (still present on main at 9767ba2)