Rewritten after review. The original framing called the pinned aliases a "silent downgrade".
That was wrong: on Vertex, model access is per-project, so pinning aliases to specific ids that
are known-enabled is deliberate and safer than resolving to whatever is newest. The real bug is
narrower — see below.
Summary
fable is not in piModelAliases, so on pi it falls through the bare-id path to
anthropic-vertex/fable — a model no provider registers. pi does not error: it substitutes a
fallback model with the wrong wire id and only warns, and that warning is invisible in a normal run.
The bug
var piModelAliases = map[string]string{
"opus": "claude-opus-4-6",
"sonnet": "claude-sonnet-4-6",
"haiku": "claude-haiku-4-5",
}
Probed through translatePiModel on main:
harness model: |
resolves to |
registered? |
opus |
anthropic-vertex/claude-opus-4-6 |
yes |
sonnet |
anthropic-vertex/claude-sonnet-4-6 |
yes |
haiku |
anthropic-vertex/claude-haiku-4-5 |
yes |
fable |
anthropic-vertex/fable |
no |
fable is documented as a valid alias in docs/runtimes/claude.md and docs/runtimes.md, and
Claude Code accepts it natively — so a harness that works on claude breaks on pi, and breaks
quietly.
What happens on the pi side: resolveCliModel finds no match and calls buildFallbackModel, which
clones a real model but replaces its id with the unresolvable string, returns error: undefined and
only a warning. buildPiRunCommand sends pi's stderr to pi-debug.log only when --debug is
set, so in a normal run nothing surfaces. This is the same silent-substitution class the
xai/grok-4.6 normalisation was added to close in #6571.
Suggested fix
- Add
fable to the alias table, mapped to a model id enabled in the fleet's Vertex project.
- Fail loudly on an unmapped alias. Anything in a known alias vocabulary with no mapping should
be a startup error naming the alias, rather than a bare id that cannot resolve and gets silently
substituted. This is the part worth fixing regardless of which ids the table holds.
- Consider a test asserting every alias in the table resolves to a model the pinned pi version
actually registers — catching a typo or a removed id locally rather than in a live run. It should
assert registration, not recency: pinning to older, known-enabled ids is intentional.
Not a bug: the pinned ids
opus and sonnet map to 4-6 while pi's catalog also lists claude-opus-5 / claude-sonnet-5.
That is deliberate. Vertex enables models per project, so the newest id in pi's catalog is not
necessarily accessible to a given project, and resolving an alias to "latest" would break runs in
projects that do not have it. Claude Code resolves aliases natively to the current tier model, which
carries that same risk on Vertex — worth knowing, but not something to fix here.
Docs follow-on
docs/runtimes/claude.md says aliases resolve to "the current Anthropic model of that tier". On
Vertex that is not guaranteed to be accessible — per-project enablement decides. Both runtime pages
should say that model availability is per-project on Vertex, and that pi's alias table is a
fullsend-owned pinned mapping rather than a passthrough.
Summary
fableis not inpiModelAliases, so on pi it falls through the bare-id path toanthropic-vertex/fable— a model no provider registers. pi does not error: it substitutes afallback model with the wrong wire id and only warns, and that warning is invisible in a normal run.
The bug
Probed through
translatePiModelonmain:model:opusanthropic-vertex/claude-opus-4-6sonnetanthropic-vertex/claude-sonnet-4-6haikuanthropic-vertex/claude-haiku-4-5fableanthropic-vertex/fablefableis documented as a valid alias indocs/runtimes/claude.mdanddocs/runtimes.md, andClaude Code accepts it natively — so a harness that works on
claudebreaks onpi, and breaksquietly.
What happens on the pi side:
resolveCliModelfinds no match and callsbuildFallbackModel, whichclones a real model but replaces its id with the unresolvable string, returns
error: undefinedandonly a warning.
buildPiRunCommandsends pi's stderr topi-debug.logonly when--debugisset, so in a normal run nothing surfaces. This is the same silent-substitution class the
xai/grok-4.6normalisation was added to close in #6571.Suggested fix
fableto the alias table, mapped to a model id enabled in the fleet's Vertex project.be a startup error naming the alias, rather than a bare id that cannot resolve and gets silently
substituted. This is the part worth fixing regardless of which ids the table holds.
actually registers — catching a typo or a removed id locally rather than in a live run. It should
assert registration, not recency: pinning to older, known-enabled ids is intentional.
Not a bug: the pinned ids
opusandsonnetmap to 4-6 while pi's catalog also listsclaude-opus-5/claude-sonnet-5.That is deliberate. Vertex enables models per project, so the newest id in pi's catalog is not
necessarily accessible to a given project, and resolving an alias to "latest" would break runs in
projects that do not have it. Claude Code resolves aliases natively to the current tier model, which
carries that same risk on Vertex — worth knowing, but not something to fix here.
Docs follow-on
docs/runtimes/claude.mdsays aliases resolve to "the current Anthropic model of that tier". OnVertex that is not guaranteed to be accessible — per-project enablement decides. Both runtime pages
should say that model availability is per-project on Vertex, and that pi's alias table is a
fullsend-owned pinned mapping rather than a passthrough.