feat(fleet): approved replacement routes for refused first requests - #6438
Merged
Merged
Conversation
A saved reviewer pin (xai/grok-4.6) answered its first request with "Authorization failed: You have run out of credits or need a Grok subscription." and the agent failed with no review. The only fallback was the pre-flight SessionFallback for unusable pins; nothing covered a provider refusing the request itself, and saved routes must not be silently swapped. `[subagents.roles.<role>] replacements = ["provider/model", ...]` lets the operator approve up to three replacement routes. When the pin's first request fails with a typed route refusal (QuotaExhausted, AuthenticationError, AuthorizationError, ModelError) and the agent has run nothing yet (run_subagent's steps == 1 seam), the same request moves to the next approved route. The run keeps its role, grants, tool scope, workspace and budgets: only the request route changes, bound through the existing provider/model/protocol binders, and each route is tried once. The existing route receipt records the effective provider/model, `route_source = "role.replacement"`, and a fallback note with the original route, reason, provider message (redacted) and attempt. Not replaced: failures after any tool ran, content-policy / context-length / invalid-request errors, untyped messages that merely mention credits, Codewhale permission denials, exact Fleet members and task-level models. Entries must name their provider explicitly, so the destination that may receive the task is always operator-chosen; misconfiguration fails at spawn. Replacement authority is cleared for every other route source and never inherited by descendants. The observed Grok error is authorization-class (403), not QuotaExhausted. Evidence: 114 passed, 0 failed (13,142 skipped) across route_replacement, roster_routes, launch_receipt, fatal-provider, typed-retry and role-pin selections; then 5/5 route_replacement tests after adding the spawn refusal case. The end-to-end test runs a local 403 fixture with the observed text and an approved backup fixture: the pin is asked once, the backup completes the review, and the receipt names both routes. A pin without replacements still fails and never contacts the backup. TUI all-target/all-feature Clippy with CI flags and fmt passed; dead code budget unchanged at 279. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The blocking-call ratchet counted the fixture's std::fs::write inside an async test path (Lint on #6438). Evidence: route_replacement 5 passed, 0 failed; blocking-call budget 577 sites, within budget. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to Mu-L/DeepSeek-TUI
that referenced
this pull request
Sep 24, 2026
turn_operation_lookup_is_authenticated_read_only_and_survives_restart failed on Ubuntu CI (Hmbown#6438 run 35886856223, job 107269234021) with "Mock Runtime execution scope is already owned" and passed 3/3 locally. The old test server tears its runtime down on its own thread after the abort, so its mock TaskManager can still hold the execution-scope owner lock after the RuntimeThreadManager the test waits on has dropped. Refusing a second owner is correct product behavior; the restart now retries only on that refusal, within the existing CI-scaled 10 s deadline, and any other startup error still fails immediately. Evidence: the test passes locally (1 passed, 0 failed); hosted load is the acceptance. TUI all-target/all-feature Clippy with CI flags passed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hmbown
pushed a commit
that referenced
this pull request
Sep 24, 2026
Resolves the child request setup against #6438's route override: the request and the child's compaction both use the active route_runtime, so a replacement route's client summarizes its own history. Tests: cargo test -p codewhale-tui --lib -- tools::subagent compaction => 870 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No-Issue: 0.10.1 release-readiness recovery and first-call tool work, found in dogfooding; no public issue tracks it.
A saved reviewer pin (xai/grok-4.6) answered its first request with
"Authorization failed: You have run out of credits or need a Grok
subscription." and the agent failed with no review. The only fallback
was the pre-flight SessionFallback for unusable pins; nothing covered a
provider refusing the request itself, and saved routes must not be
silently swapped.
[subagents.roles.<role>] replacements = ["provider/model", ...]letsthe operator approve up to three replacement routes. When the pin's
first request fails with a typed route refusal (QuotaExhausted,
AuthenticationError, AuthorizationError, ModelError) and the agent has
run nothing yet (run_subagent's steps == 1 seam), the same request moves
to the next approved route. The run keeps its role, grants, tool scope,
workspace and budgets: only the request route changes, bound through
the existing provider/model/protocol binders, and each route is tried
once. The existing route receipt records the effective provider/model,
route_source = "role.replacement", and a fallback note with theoriginal route, reason, provider message (redacted) and attempt.
Not replaced: failures after any tool ran, content-policy /
context-length / invalid-request errors, untyped messages that merely
mention credits, Codewhale permission denials, exact Fleet members and
task-level models. Entries must name their provider explicitly, so the
destination that may receive the task is always operator-chosen;
misconfiguration fails at spawn. Replacement authority is cleared for
every other route source and never inherited by descendants. The
observed Grok error is authorization-class (403), not QuotaExhausted.
Evidence: 114 passed, 0 failed (13,142 skipped) across route_replacement,
roster_routes, launch_receipt, fatal-provider, typed-retry and role-pin
selections; then 5/5 route_replacement tests after adding the spawn
refusal case. The end-to-end test runs a local 403 fixture with the
observed text and an approved backup fixture: the pin is asked once,
the backup completes the review, and the receipt names both routes. A
pin without replacements still fails and never contacts the backup.
TUI all-target/all-feature Clippy with CI flags and fmt passed; dead
code budget unchanged at 279.
🤖 Generated with Claude Code