Skip to content

Agent pane: a turn whose model can't be reached says so, not Thinking (acpmux turn_retrying) - #34306

Merged
teamleaderleo merged 4 commits into
feat-cmux-nextfrom
lane/api-retry-status
Nov 3, 2026
Merged

teamleaderleo merged 4 commits into
feat-cmux-nextfrom
lane/api-retry-status

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Leo, 2026-11-03, on a chat that read "Thinking 7m 49s": "i feel like this mac session just keeps "thinking" but idk if tool calls are somehow being hidden or if that workspace itself/session itself under acpmux is kinda weird".

The chat was not thinking. Its model gateway was down, and Claude Code was retrying the request (system/api_retry, attempt 1..10, error_status, error, about 75 s apart). acpmux logged those lines but nothing the pane reads said so, so the live line kept reading "Thinking" with a clock that counted the retries.

  • acpmux (hub/turn_retry.rs): each harness retry of a failed model request logs a mux record turn_retrying {turnId, attempt, maxRetries, error, errorStatus, retryDelayMs, source}. Sources: Claude Code's system/api_retry line and Codex's session_info_update._meta.codex.error with willRetry ("Reconnecting... 2/5", HTTP status from codexErrorInfo). It is in the transcript category, so clients get it with no new subscription. The turn is marked silent, so the model's next sign logs turn_heard, which ends the retry. Documented in the README and docs/acpmux-schema.json.
  • Agent pane: direct.ts keeps the retry on a client-only TURN_RETRY row (kind typing, drawn by nothing). turns.ts withRetry puts it on the live line, and Thinking.tsx then reads "Can't reach the model · retrying (7 of 10) · 503 server error", or "The model is busy" for 429/529 and rate-limit or overload errors. It is calm: no shimmer, no clock, the cause in text ink and the count and error muted, with the error the part that truncates. It uses the same .cv-worked box, so nothing moves when it starts or ends. Once the model answers, the retries' time comes off the "Thinking" clock (before the first output, the clock times the turn from its prompt).
  • Given up: a retry that runs out still ends the turn failed, shown on the existing turn error card with Retry ("API Error: Connection error." reads as unreachable). That needed no change; the gallery shows it.
  • The iPhone app already reads claude.system.api_retry ("Retrying (2 of 10, error 503)"), so this adds no parity gap.

Gallery: agent-pane.turn-retry, with variants retrying, retrying-with-error, retrying-busy and given-up. The mock daemon has the live scene at ?mock&session=mock-model-retry (seven turn_retrying records).

Testing

  • acpmux, on fleet cmux-ci test (Linux): cargo clippy -p acpmux --all-targets -D warnings is clean. cargo test -p acpmux --lib turn_retry (3 unit tests) passes. --test claude_stream_timing passes; its new a_claude_api_retry_logs_turn_retrying_until_the_model_answers uses fake Claude's api-retry-probe. --test hub_integration -- retry silent passes 44 tests, including the Codex retry test, which now asserts turn_retrying comes before turn_heard, which comes before the redelivered answer. cargo fmt was fixed in the second commit.
  • Webviews: turnRetry.live.test.ts feeds real records through AcpmuxDirectClient over the mock socket and checks the retry row, the retry time, and the cleanup at turn end. It also covers withRetry and retryParts. test/transcript-turn-retry.test.tsx renders the gallery snapshots through turnView and Thinking.

Observed / Not run

  • Observed: the Rust results above. The webview run's results are posted as a comment.
  • Not run: a tagged DEV build against a real failing gateway.

Changelog

Fixed: A chat whose model can't be reached now says "Can't reach the model · retrying (N of M)" instead of "Thinking".

Proof

No capture: webview UI. See gallery agent-pane.turn-retry.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Shows when a chat's model can't be reached instead of reading "Thinking" while the harness retries the request.

Previously a turn whose model gateway was down kept its "Thinking" live line with a clock counting the retries. Now acpmux logs each retry, and the agent pane's live line reads "Can't reach the model · retrying (7 of 10) · 503 server error" (or "The model is busy" for 429/529 and rate-limit or overload errors, including status-less codes like rate_limit_error), calmly, with no sweep and no clock. When the model answers, the line returns to "Thinking" minus the time the retries took. Once a retry runs out, the turn still ends failed on the existing error card with Retry.

acpmux

  • Each harness retry of a failed model request logs a turn_retrying mux record (transcript category), sourced from Claude Code's system/api_retry and Codex's _meta.codex.error with willRetry.
  • The retry marks the turn silent, so the model's next sign logs turn_heard, ending the retry.

Agent pane

  • The live line carries a client-only TURN_RETRY row, drawn in the same .cv-worked box so nothing moves when retries start or end.
  • No new subscription needed; existing transcript clients receive the records.

Written for commit 0e5fc23. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 2 commits November 3, 2026 14:57
…ot Thinking

Leo, 2026-11-03, on "Thinking 7m 49s" from a chat whose model gateway was
down: "i feel like this mac session just keeps "thinking" but idk if tool
calls are somehow being hidden or if that workspace itself/session itself
under acpmux is kinda weird".

acpmux logs each harness retry of a failed model request as turn_retrying
{turnId, attempt, maxRetries, error, errorStatus, retryDelayMs, source}:
Claude Code's system/api_retry lines and Codex's willRetry stream errors
(hub/turn_retry.rs), in the transcript category. The model's next sign logs
turn_heard. The agent pane's live line then reads "Can't reach the model ·
retrying (7 of 10) · 503 server error" (or "The model is busy" for 429/529),
calm, with no sweep and no clock, in the same box; once the model answers,
the retries' time leaves the Thinking clock. A retry that runs out ends the
turn failed on the existing error card with Retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 0e5fc23: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr34306 -f ref=lane/api-retry-status -f platform=macos -f backend_mode=local

Gallery before/after for 0e5fc23: the capture produced no images (run).

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on f382257 to compare; do not merge until green; see lane-rules

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at a51f918

Reviewer: codex gpt-6-astra high, fast tier.

Follow-ups: 3. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward.

SHIP at a51f918
Visual verdict: no images; Leo tries the build

  1. [P2] [follow-up] cmux-tui/crates/acpmux/src/hub/turn_retry.rs:103 — Unrelated progress clears the retry warning. The shared silence handler accepts subagent output and claude.tool_progress, so either can emit turn_heard while the main request is still retrying. Track retry recovery separately and test a retry followed by subagent/tool progress before the main model answers.

  2. [P2] [follow-up] webviews/src/agent-session/acpmux/conversation/Thinking.tsx:36 — Without an HTTP status, rate_limit_error and overloaded_error incorrectly produce “Can't reach the model.” The regex’s trailing word boundary fails before _error. Normalize underscores before matching or explicitly accept these values; add status-less cases to the tests.

  3. [P2] [follow-up] webviews/src/agent-session/acpmux/conversation/TurnRetry.gallery.ts:19 — Capture the gallery variants, including a narrow pane, to verify truncation and layout.

Cargo and Bun were unavailable; source review and the extracted error-label reproduction completed.

Follow-ups:

  • Keep retry warnings active through unrelated progress.
  • Recognize status-less busy error codes.
  • Capture and inspect the retry gallery.

Router review follow-up on #34306: the busy regex's word boundary missed codes with a trailing _error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa

@teamleaderleo teamleaderleo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (codex gpt-6-astra high, next-review, since=20261103T232347Z): SHIP at d32ae48

SHIP at d32ae48
Visual verdict: no images; Leo tries the build

  1. [P2] [follow-up] turn_retry.rs:103: Background subagent progress clears the retry warning prematurely. The reused silent_turn state is consumed by heard_after_silence for subagent_progress, child stream events, and tool updates—not necessarily recovery of the failed model request. Keep retry recovery separate from general activity detection and test a main request retrying while a background subagent emits progress.

  2. [P2] [follow-up] direct.ts:6218: The warning can remain after successful recovery across a daemon restart. silent_turn initializes empty and host adoption does not restore it, so the next successful response emits no turn_heard. The client replays turn_retrying and keeps displaying it until turn end. Restore the pending retry during adoption and cover restart between retry and recovery.

  3. [P2] [follow-up] turns.ts:1155: Retry display is skipped when agent.sessionStatus is enabled and a todo list produces an ACTIVITY_LINE; layouts producing WORKING also bypass this guard. Those lines continue showing ordinary activity and elapsed time during an outage. Handle these supported live-line variants and add coverage.

  4. [P2] [follow-up] TurnRetry.gallery.ts:19: Supply rendered gallery images, including a narrow pane, to verify truncation and layout stability.

Bun and Cargo were unavailable, so tests could not be rerun.

Follow-ups:

  • Scope retry recovery to the affected model request.
  • Restore pending retries during daemon adoption.
  • Render retry status across live-line variants.
  • Capture gallery images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
@teamleaderleo teamleaderleo added the leo-named-fix An obvious fix to something Leo named: lands on push, review runs in parallel label Nov 3, 2026
@blacksmith-sh

blacksmith-sh Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Found 43 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpby0_enzm/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpkw3ni25m/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs

...and 33 more test failures. View all on Blacksmith

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo
teamleaderleo merged commit 8f918fa into feat-cmux-next Nov 3, 2026
193 of 209 checks passed
@teamleaderleo
teamleaderleo deleted the lane/api-retry-status branch November 3, 2026 23:37
lawrencecchen added a commit that referenced this pull request Nov 4, 2026
…t, not retry["turnId"] =

8f918fa (#34306) writes retry["turnId"] with IndexMut on a serde_json Value, which
clippy::indexing_slicing counts. cmux_json::JsonSet::set writes the same key on an object
(and on null, as IndexMut does) and never panics; a non-object retry is left as it is.

Sites from the panic step's clippy JSON: last green tip ab54fff (fleet step 294210d667444f201633a143) against feat 851f9ed plus this branch's fixes, with the acpmux and cmux-tui rows raised on a throwaway branch for the diagnosis only (fleet step 20552d31e2aa6f074ebbba6e).
lawrencecchen added a commit that referenced this pull request Nov 4, 2026
…uggestion gate, and link sync index reads (cmux-automations 1, cmux-tui, acpmux) (#34735)

* base red: crash ratchet: notification.clear reads notification_id with a typed error, not expect

7467cdd (#34498) added `value.as_str().expect("catalog resource-id
validation")` for the new notification_id field of notification.clear,
which took the cmux-tui-core expect row from 190 to 191 and turned the
crash ratchet red on feat-cmux-next. A non-string notification_id now
returns the same validation error that notification_workspace returns
for workspace_id (#23530 did the same for that field).

resource_router.rs: 7 expects at 7467cdd^, 8 at 7467cdd, 7 here.
`python3 scripts/cmux-next/crash_ratchet.py`: red at 6767426
(expect 190 -> 191), ok here. The catalog validates the field as a
resource id first, so no request reaches the old expect; no behavioral
test can fail on the base, and the ratchet is the regression check.

* base red: panic ratchet: cmux-automations import reads the ledger id with get, not v["automation"]

The panic ratchet (scripts/cmux-next/rust_panic_ratchet.py) on
feat-cmux-next reports "cmux-automations: clippy::indexing_slicing
1 > new crate (must be 0)". cmux-automations is a new crate in the
ratchet, so it must have 0 sites.

The site is Store::import in store.rs. It reads the import ledger entry
with v["automation"] on a serde_json::Value. Commit dbb578b
("teleport between this Mac, cmux Cloud and ssh:HOST; MCP automation
tools", pushed directly, with no PR) added it.

The fix uses v.get("automation").and_then(Value::as_str). Behavior does
not change: the Index on a Value gives Null when the key is missing or
the value is not an object, and as_str on Null gives None. get gives
None in the same cases. Thus the id is "" in the same cases as before.

The baseline is not raised. No allow and no crash-allow is added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* base red: panic ratchet: cmux automation's Cloud edit and audit set params with JsonSet, not params["..."] =

dbb578b (teleport between this Mac, cmux Cloud and ssh:HOST) added
three IndexMut writes on a serde_json Value in cli/automation.rs (the
Cloud `edit` name and paused fields, and the `audit` automation filter).
clippy::indexing_slicing counts each one, which took cmux-tui from 831 to
834 and kept the panic ratchet red on feat-cmux-next. cmux_json::JsonSet
sets the same keys on the same objects and never panics.

Sites from the panic step's clippy JSON: the last green tip ab54fff
(fleet step 294210d667444f201633a143) against this branch's parent with
the cmux-tui row raised for the diagnosis only (fleet step
33bab4d69e741549bf0dac14): +3 in cmux-tui, all at cli/automation.rs
1338, 1341 and 1380. Behavior is the same: each params value is a JSON
object built by json!({...}) just before.

* base red: panic ratchet: acpmux task plan updates its task with get_mut, not self.tasks[at]

f61b2d9 (#34567) indexes self.tasks[at] in TaskPlan's update, and clippy::indexing_slicing
counts it (acpmux 689 > 686 with the next two commits). at comes from position() just
before, so get_mut(at)? finds the same task and never returns None there.

Sites from the panic step's clippy JSON: last green tip ab54fff (fleet step 294210d667444f201633a143) against feat 851f9ed plus this branch's fixes, with the acpmux and cmux-tui rows raised on a throwaway branch for the diagnosis only (fleet step 20552d31e2aa6f074ebbba6e).

* base red: panic ratchet: acpmux turn_retrying sets turnId with JsonSet, not retry["turnId"] =

8f918fa (#34306) writes retry["turnId"] with IndexMut on a serde_json Value, which
clippy::indexing_slicing counts. cmux_json::JsonSet::set writes the same key on an object
(and on null, as IndexMut does) and never panics; a non-object retry is left as it is.

Sites from the panic step's clippy JSON: last green tip ab54fff (fleet step 294210d667444f201633a143) against feat 851f9ed plus this branch's fixes, with the acpmux and cmux-tui rows raised on a throwaway branch for the diagnosis only (fleet step 20552d31e2aa6f074ebbba6e).

* base red: panic ratchet: acpmux suggestion gate marks a withheld suggestion with JsonSet, not nested IndexMut

a4f11cd (#34496) writes _meta.acpmux.suggestionWithheld through nested IndexMut, which
clippy::indexing_slicing counts (3 new hits). The same writes now go through
cmux_json::JsonSet and get_mut: a non-object _meta or _meta.acpmux still becomes {} first,
and an update that is not an object (where IndexMut would panic) is left as it is.

Sites from the panic step's clippy JSON: last green tip ab54fff (fleet step 294210d667444f201633a143) against feat 851f9ed plus this branch's fixes, with the acpmux and cmux-tui rows raised on a throwaway branch for the diagnosis only (fleet step 20552d31e2aa6f074ebbba6e).

* base red: panic ratchet: cmux link sync's relative link and readlink answer read without an index

6b9bafd (#34362) added &to[common..] in relative_from and answer["target"] in
readlink; clippy::indexing_slicing counts both (cmux-tui +2 over 831 after the teleport
fix in this branch). common is at most to.len(), so to.get(common..) gives the same
slice; answer.get("target") gives None where answer["target"] gave Null, and both end
in the same "fs.readlink answered no target" error.

Sites from the panic step's clippy JSON: last green tip ab54fff (fleet step 294210d667444f201633a143) against feat 851f9ed plus this branch's fixes, with the acpmux and cmux-tui rows raised on a throwaway branch for the diagnosis only (fleet step 20552d31e2aa6f074ebbba6e).

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 0e5fc231 Deployed Nov 3, 2026 by teamleaderleo via wait for same-key tree publisher #108041
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

leo-named-fix An obvious fix to something Leo named: lands on push, review runs in parallel risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant