Skip to content

fix(tui): clear model status rows once the connection recovers - #223

Merged
kingsword09 merged 1 commit into
mainfrom
fix/tui-model-status-row
Oct 19, 2026
Merged

kingsword09 merged 1 commit into
mainfrom
fix/tui-model-status-row

Conversation

@kingsword09

@kingsword09 kingsword09 commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Problem

When a model request fails, the TUI prints Model request failed (red, Ctrl+O to expand). The row survives a successful retry, and every failure appends a new one, so a single turn can pile up several of them.

The cause is not "retry failed" — the TUI treated each failed attempt as a permanent transcript record:

  • model_request_failed with retryable !== true appended a row under a random block id (packages/zcode-tui/src/index.ts).
  • But retryable: false does not end the turn. When streamed output was already committed, the runtime retry gate returns "not retryable" (emittedRetryBoundaryEvent), and the turn level then recovers the same request via recoverFromModelFailure (stream recovery, surfaced as Recovering model stream) or a model fallback, and continues successfully with a new attempt.
  • So every "stream dropped after output" left a red error row plus a recovery row; the red row never cleared, and repeated drops stacked up.

Same class of problem: model_stream_stalled also appended a new row per stall.

Change

Model network status is per-attempt turn status, not a transcript record:

  • stalled / failed / retrying / recovering share one per-turn status row (renamed modelRetryBlockId to modelStatusBlockId) that is replaced in place instead of appending.
  • The row is removed on model_request_completed (clearModelStatus()): once the connection is back, nothing is left behind.
  • Unrecoverable failures still keep their Model request failed row; cancellations (AbortError) are still ignored; retryable: true failures still only update the activity line so they do not flicker against the retry row that follows.

Result: Model stream stalled · idle 60s -> Retrying model request · 1/5 · in 1s -> request completes -> the row disappears.

Tests

  • New e2e: test/tui/model-status.test.ts plus test/tui/fixtures/model-status.ts (registered in test:tui:e2e).
    • After two recovered "stall -> failure -> turn-level recovery -> success" cycles, the screen contains none of Model request failed, Model stream stalled, Retrying model request, Recovering model stream; the stalled status is still visible while the attempt is unresolved.
    • A following unrecoverable failure still shows exactly one Model request failed row.
    • Negative checks: removing clearModelStatus() makes the test fail (row stays); reverting the whole change makes it fail (rows accumulate).
  • test/fixtures/tui-features.ts: the status row is now cleared on success, so stalled and retrying get 400 ms observation windows to keep the scripts/smoke-tui-features.ts assertions valid.
  • Run locally: bun run typecheck; bun run test:tui:e2e (35 passed); bun scripts/smoke-tui-features.ts (several runs); bun scripts/smoke-tui-pressure.ts; bun run test:unit (851 passed, 2 failures are local environment issues from a missing synced runtime metadata manifest, unrelated to this change).

Known gap

If a turn is cancelled with Esc while waiting for a retry, no model_request_completed arrives and the Retrying model request row remains (same as before this change). Clearing it would require distinguishing "in progress" from "failed" status rows at turn end, which is a reasonable follow-up.

A `model_request_failed` event with `retryable: false` does not end the
turn: when streamed output was already committed the runtime disables the
attempt-level retry and then recovers the same request at the turn level
(stream recovery / model fallback). The transcript rendered a permanent
error row per such attempt, so every dropped stream left a stale
"Model request failed" row behind and they accumulated.

Model network status is per-attempt turn status, not a transcript record:
stalled, failed, retrying and recovering events now share one per-turn
status row that is replaced in place, and the row is removed as soon as a
model request completes, so nothing is left behind after reconnecting.
Unrecoverable failures still keep their row, and cancellations are still
ignored.

Adds an end-to-end scenario that recovers two stalled/failed attempts and
asserts no status row survives, while a terminal failure stays visible.
@github-actions

Copy link
Copy Markdown
Contributor

pkg-pr-new preview for commit dd2c4a5a7beaf7f116b108271a34f88fc0adca9e.

npx --yes https://pkg.pr.new/zcode-app-cli@dd2c4a5

Full repository URL

This preview uses the selected commit. PR updates publish automatically after CI succeeds; kingsword09 can also request a preview with /pkg-pr-new.

Workflow logs

@kingsword09
kingsword09 merged commit 0482b37 into main Oct 19, 2026
5 checks passed
@kingsword09
kingsword09 deleted the fix/tui-model-status-row branch October 19, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant