Skip to content

fix:(tui) reset stale streaming state after event gaps#62602

Closed
sahilsatralkar wants to merge 6 commits into
openclaw:mainfrom
sahilsatralkar:fix/issue-62395-tui-streaming-gap-reset
Closed

fix:(tui) reset stale streaming state after event gaps#62602
sahilsatralkar wants to merge 6 commits into
openclaw:mainfrom
sahilsatralkar:fix/issue-62395-tui-streaming-gap-reset

Conversation

@sahilsatralkar

@sahilsatralkar sahilsatralkar commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Describe the problem and fix in 2–5 bullets:

If this PR fixes a plugin beta-release blocker, title it fix(<plugin-id>): beta blocker - <summary> and link the matching Beta blocker: <plugin-name> - <summary> issue labeled beta-blocker. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.

  • Problem: TUI could stay stuck in streaming after a run effectively ended when a gateway event gap/desync happened.
  • Why it matters: the status line looked busy indefinitely and stale activeChatRunId could block clean progress to the next run.
  • What changed: added explicit TUI gap-reset handling to clear stale active run state and wired it into client.onGap (with reload) and client.onDisconnected (no reload).
  • What did NOT change (scope boundary): no protocol/gateway contract changes, no plugin/channel behavior changes, no config/schema changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause: event-gap recovery did not clear stale activeChatRunId / streaming state in the TUI state machine.
  • Missing detection / guardrail: no gap-specific reset path test to assert transition back to idle and acceptance of a fresh run.
  • Contributing context (if known): TUI relied on normal final/aborted/error chat events for cleanup, but gap/desync can skip those events.

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
    • Target test or file: src/tui/tui-event-handlers.test.ts
    • Scenario the test should lock in: a stream receives delta, then a gap reset occurs, state returns to idle, stale run is cleared, and next run streams normally.
    • Why this is the smallest reliable guardrail: the bug is TUI state-transition logic in event handlers; unit coverage is direct and deterministic.
    • Existing test that already covers this (if any): none specific to gap reset prior to this.
    • If no new test is added, why not: N/A (new tests added).

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

  • TUI now recovers to idle after an event gap instead of potentially remaining stuck in streaming.
  • After a gap reset, subsequent runs can become the active stream normally.

Diagram (if applicable)

For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write N/A.

Before:
[event gap during stream] -> [stale activeChatRunId remains] -> [status can stay "streaming"]

After:
[event gap during stream] -> [handleEventGap clears stale run + sets idle] -> [history refresh] -> [next run streams normally]

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS (local dev machine)
  • Runtime/container: Node v25.5.0, pnpm
  • Model/provider: N/A
  • Integration/channel (if any): TUI + gateway chat event stream
  • Relevant config (redacted): default local dev config

Steps

  1. Start TUI and begin a chat stream (delta events).
  2. Trigger/observe an event-gap/desync path.
  3. Check activity state and ability to continue with a new run.

Expected

  • Activity returns to idle after gap recovery.
  • Stale active run is cleared.
  • New run can stream normally.

Actual

  • Added and passed unit tests confirming gap reset and non-regression for active streams.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: ran targeted test files manually:
    • pnpm test src/tui/tui-event-handlers.test.ts (24 passed)
    • pnpm test src/tui/tui.test.ts (27 passed)
  • Edge cases checked: gap reset clears stale run; ongoing valid stream is not cleared.
  • What you did not verify: full repo test matrix; manual interactive end-to-end gap reproduction in live TUI session.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: gap handling may clear an active run aggressively in borderline transient conditions.
  • Mitigation: scoped reset only on explicit onGap/disconnect paths plus regression tests for normal active-stream continuity.

Built with Codex

@greptile-apps

greptile-apps Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds handleEventGap to tui-event-handlers.ts to explicitly clear stale activeChatRunId and streaming state on event gap or disconnect, then wires it into client.onGap (with reload) and client.onDisconnected (without reload) in tui.ts. Two regression tests cover gap-reset-to-idle and non-disruption of live streams.

Confidence Score: 5/5

Safe to merge — scoped TUI state fix with no protocol, schema, or plugin contract changes.

All remaining findings are P2 or lower. The fix correctly mirrors the cleanup logic of terminateRun (dropping the assembler entry, clearing sessionRuns, nulling activeChatRunId, setting idle status) and is consistent with existing abort/error handling — neither that path nor this one calls chatLog.dropAssistant directly; partial content is replaced by the subsequent history reload. One minor observation: when multiple runs are present in sessionRuns at gap time, only the active run's assembler entry is explicitly dropped while the rest are orphaned in the TuiStreamAssembler map — but they are bounded, unreachable after sessionRuns.clear(), and not a functional regression.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(tui): reset stale streaming state af..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63774fdef1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/tui/tui-event-handlers.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1722974f36

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/tui/tui-event-handlers.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9b5144fcb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +461 to +462
if (shouldReload) {
refreshAfterGap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve optimistic turn before reloading history on gap

When a gap occurs during an active run, this path always calls loadHistory(), even if pendingOptimisticUserMessage is true. In that case (run-a active, user already sent run-b), loadHistory clears the chat log and can remove the optimistic user turn for run-b; later, because run-b is pre-tagged local, its normal final path often skips history reload, so the missing user turn can persist in the UI. The no-active-run branch already special-cases pending optimistic sends, and the active-run branch needs the same guard.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ignoring this comment for now as it is P2

…treaming-gap-reset

# Conflicts:
#	src/tui/tui-event-handlers.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2040974ad2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// to recover if the stream resumes with the same run id.
streamAssembler.drop(previousRunId);
sessionRuns.clear();
state.activeChatRunId = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Keep abortable run id when clearing gap state

Clearing state.activeChatRunId here makes /abort unusable after a gap/disconnect even when the backend run is still executing. abortActive in src/tui/tui-session-actions.ts requires this field and immediately returns "no active run" when it is null, so after onGap/onDisconnected we can no longer cancel a still-running job until another chat event rebinds the run id (which may never happen for a dropped stream).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ignoring this for now as it is P2

…treaming-gap-reset

# Conflicts:
#	src/tui/tui-event-handlers.test.ts
#	src/tui/tui.ts
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling labels Apr 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5425f5346a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +205 to +206
if (gapClearedRunId === params.runId) {
gapClearedRunId = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Keep gap guard until optimistic run is actually claimed

Clearing gapClearedRunId as soon as the pre-gap run reaches final/aborted/error lets a foreign run consume pendingOptimisticUserMessage before the local pending run emits its first event. In the sequence run-a active -> local run-b queued (pending=true) -> gap -> run-a final -> foreign run-x delta, the (!gapClearedRunId || isKnownLocalRun) check becomes true again for run-x, so run-x is incorrectly marked local and the pending flag is consumed. Fresh evidence is that this new early clear re-opens the claim path even though the optimistic local run has not been bound yet.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Closing this as duplicate or superseded after Codex automated review.

PR #62602 is superseded by merged PR #67401, which shipped the current TUI streaming watchdog in v2026.4.15. Current main resets stale streaming state after lost or late final chat events, has regression coverage, and the older explicit event-gap reset patch still carries unresolved optimistic-binding and abortability risks.

Best possible solution:

Close #62602 as superseded by shipped PR #67401. Keep the v2026.4.15 watchdog implementation on main; if maintainers still want immediate event-gap recovery, track it as a fresh focused change with tests for optimistic sends, foreign post-gap runs, history reload behavior, and /abort preservation.

What I checked:

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against d54d2d6b9b8a; fix evidence: release v2026.4.15, commit 352527393079.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui gateway Gateway runtime size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI can stay stuck in streaming state after reply is finished

1 participant