The server never fills conclusion.failure.detail.exec_output_tail, so the
Output section never appeared. Fall back to the output the stopped stage
recorded in the run state, skipping offloaded blob references and model
request failures, whose step output is the agent's text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dead run's projection never gets conclusion.failure, so the banner
never showed. Fall back to the run summary's lifecycle.error, where the
server keeps a dead run's reason, and to a generic headline when that is
empty too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status popover shows the reason code ("Workflow error") and
lifecycle.error, which is empty when the failure lives in the run's
state, so a run stopped by the provider's billing refusal showed no
reason anywhere. The message is in conclusion.failure.detail, which the
run page already loads.
Show a quiet banner on failed and dead runs: the step the run stopped
at, a headline and the next thing to do, with the verbatim message and
the tail of a failed command's output in a closed <details>. The advice
comes from the failure's reason and category first (approval denied,
budget, publish, launch), then from the structured model request
message the server records (provider, kind, status, code), then from a
command's exit status. Command output is never searched for provider
or billing words. Provider names come from /providers, cancelled runs
get no banner, and a step that failed earlier but was recovered from
isn't named as the stop.
Fabro's run, session, turn, message and pair IDs are ULIDs. A ULID
occasionally has enough distinct characters to cross the secret
scanner's 4.5-bit entropy threshold, and the stream redaction then
rewrites the Web UI link to `/runs/REDACTED` and the run branch to
`fabro/run/REDACTED`. Two nightly releases in the last week failed on
`attach_replays_completed_detached_run` this way, and each fix so far
protected one field at a time.
Teach the entropy scanner the format instead: a token that is exactly
26 uppercase Crockford base32 characters and decodes to a timestamp
between 2020 and 2100 is an identifier, not a secret, and is left
alone wherever it appears. Trailing `-ULID`, `_ULID` and `=ULID`
segments are stripped before measuring, so `20261020-<id>` and
`<secret>-<id>` are handled, and the secret in the latter is still
found. The gitleaks credential patterns run on every token as before.
This mirrors detect-secrets' built-in UUID filter. A secret would have
to take exactly this shape and land in the date window to be missed,
which is about one in a hundred of the already rare tokens encoded as
26 uppercase base32 characters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fabro's shared resolver passed any unknown bare selector through verbatim
to the highest-priority ready provider. A provider name in the model field,
such as `gemini`, is not a catalog model, so it took that path and reached
Anthropic with wire model `gemini`. The same path turned any typo into a
request on whichever provider happened to win priority.
An unqualified unknown selector is now an error. Passthrough of an
uncatalogued model id still works, but only with a provider pin, which is
also how the operator docs already described it. A bare provider name gets
a dedicated error that names the provider and says to pin it and omit the
model to use its default.
The completions endpoint returns that error as a 400 before any provider
call; the models lookup returns it as a 404. Session creation keeps its
own provider-name message and no longer passes unknown unpinned names
through either. The OpenAPI description of `CreateCompletionRequest.model`
no longer advertises a provider id as a selector, and the TypeScript client
is regenerated from it.
Closes#950 by rejecting the input rather than resolving it: a provider
name in the model position is ambiguous with model aliases, and no
comparable library resolves it to a provider default.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>