Skip to content

fix(tui): show busy startup loader during post-connect initialization [AI-assisted]#93999

Merged
steipete merged 1 commit into
openclaw:mainfrom
ml12580:fix/vuln-74385-tui-startup-loader
Jul 6, 2026
Merged

fix(tui): show busy startup loader during post-connect initialization [AI-assisted]#93999
steipete merged 1 commit into
openclaw:mainfrom
ml12580:fix/vuln-74385-tui-startup-loader

Conversation

@ml12580

@ml12580 ml12580 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

[AI-assisted] This PR was generated using Claude Code.

Summary

  • Problem: During Terminal Hatch onboarding (and any local/gateway TUI connect), client.onConnected in src/tui/tui.ts flipped the connection status to "local ready"/"connected" immediately and then awaited post-connect initialization (refreshAgents(), restoreRememberedSession(), loadHistory()) without ever setting a busy activity status. renderStatus() only renders the spinner/loader when isTuiBusyActivityStatus(activityStatus) is true, and "idle" is not in that set — so no loader rendered while init ran. The status line read "ready" while the UI was actually still starting up, making the TUI look frozen/laggy for ~2s after each prompt during hatch.
  • Why it matters now: This is a first-run onboarding responsiveness regression (bug, regression, P2) reported against Terminal Hatch. A prior fix PR (#f02f570b3f69) was closed unmerged because it introduced a startup-error rendering regression and lacked real behavior proof.
  • Intended outcome: The TUI shows the busy status loader ("starting up") for the duration of post-connect init, then clears to the normal ready line once init actually finishes.
  • Out of scope: Any underlying init latency optimization (the async work itself is unchanged); reconnect UX beyond reusing the same busy-state path.
  • Success: Loader renders during onConnected init and clears on completion; the final ready state is byte-for-byte unchanged; startup-failure still renders the "startup failed" text (the prior PR's regression is avoided).
  • Reviewer focus: The onConnected restructure in src/tui/tui.ts — specifically that the busy activity status is cleared on both the success path and the .catch startup-failure path (so the "startup failed" static status still renders), and that the post-init ready status is unchanged.

Linked context

Closes #74385

Related: prior closed fix attempt f02f570 (closed unmerged — startup-error regression + missing real behavior proof).

Was this requested by a maintainer or owner? No — triaged from the clawsweeper:queueable-fix / clawsweeper:source-repro shortlist.

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: TUI looks frozen/laggy for ~2s during Terminal Hatch onboarding because client.onConnected flips the status to "local ready" and runs async post-connect init (agent refresh, session restore, history load) without setting a busy activity status, so renderStatus() never shows the loader.
  • Real environment tested: Linux (Ubuntu, ssh lxy-tx), Node v22.22.0, OpenClaw source tree at PR head 0a5b6eb (AFTER) and upstream main (BEFORE), driven through the real runTui()/renderStatus()/Loader render path under a real PTY (src/tui/tui-pty-harness.e2e.test.ts "shows a busy startup loader during post-connect initialization", with a slow post-connect init fixture so the loader window is visible). Also Windows 10 LTSC, Node v22.17.1 for the exported-helper reconnect-preservation check.
  • Exact steps or command run after this patch: (1) On Linux, ran the real-PTY harness test with OPENCLAW_TUI_PTY_MIRROR_PATH set to capture the actual rendered terminal bytes from the production runTui/onConnected/renderStatus path, on PR head (AFTER) and on main (BEFORE). (2) Ran node --import tsx .tmp/startup-proof.mjs importing the REAL exported helpers (isTuiBusyActivityStatus, isTuiActiveRunActivityStatus, resolveStartupActivityStatus) to show the reconnect-preservation decision.
  • Evidence after fix (terminal capture):
PTY_RENDER_AFTER (Linux, real runTui/renderStatus/Loader, PR head 0a5b6eb8f2)
status line during post-connect init:
  starting up • 0s | starting local runtime      <- busy loader renders (was missing on main)
status line once init completes:
  local ready | idle                              <- static ready line, unchanged

HELPER_AFTER (real exported resolveStartupActivityStatus)
idle               busy=false activeRun=false -> startupClaim=starting up
connecting         busy=false activeRun=false -> startupClaim=starting up
streaming          busy=true  activeRun=true  -> startupClaim=streaming      <- reconnect-owned streaming PRESERVED
running            busy=true  activeRun=true  -> startupClaim=running
finishing context  busy=true  activeRun=true  -> startupClaim=finishing context

REAL_LOCAL_RUNTIME_AFTER (Linux, real embedded backend via `tui-pty-local.e2e.test.ts`,
  only the upstream model HTTP endpoint is mocked; the embedded runtime / onConnected /
  session / history path is real — NOT the fake-backend harness)
onConnected post-connect init completes, status line reaches:
  local ready | idle
full chat round-trip against the real local runtime:
  (user) send the local PTY smoke response
  (assistant) LOCAL_PTY_RESPONSE
  • Observed result after fix: On a fresh connect the busy "starting up" loader renders during post-connect init, then clears to the static "local ready | idle" line once init finishes (ready state unchanged). On reconnect where reconnectStreamingWatchdog() has already restored streaming for a still-active run, resolveStartupActivityStatus now PRESERVES that status instead of overwriting it with starting up, and the post-init idle clear is gated so an in-flight run adopted by loadHistory() (which sets streaming) keeps streaming — reconnect no longer hides active work behind the startup loader.
  • What was not tested: Two real captures were taken on Linux: (1) the production runTui/renderStatus/Loader render path under a real PTY with a slow-init fixture showing the "starting up" loader paint (fake-backend harness — per src/tui/AGENTS.md this must not be claimed as proof of the embedded runtime itself); (2) a REAL embedded-backend local run (tui-pty-local.e2e.test.ts, only the model HTTP endpoint mocked) proving the real local runtime boots, onConnected post-connect init completes, the status reaches "local ready | idle", and a full chat round-trip works. In that real run the post-connect init was too fast on a fresh workspace for the "starting up" loader to visibly paint (the loader window is only visible when init is slow, which is the exact scenario issue [Bug]: Stuck and ultra slow during Hatch (terminal mode) - took a minute to come back up #74385 reports on heavy first-run Hatch onboarding). A real interactive Terminal Hatch onboarding run against a live backend on physical hardware with naturally slow init was not captured by the contributor (the TUI needs an interactive TTY and the contributor's Windows host cannot boot it headless). That real-backend interactive recording with slow init is still outstanding; per the ClawSweeper review a maintainer can capture it via: @openclaw-mantis visual task: capture OpenClaw TUI during slow post-connect initialization and verify the starting up loader appears without hiding active streaming after reconnect.
  • Before evidence (optional but encouraged):
PTY_RENDER_BEFORE (Linux, real runTui/renderStatus/Loader, upstream main)
"starting up" busy-loader frames rendered during post-connect init: 0
status line observed on startup:
  local ready | idle                              <- no busy loader; init shows no spinner (looks frozen)
main: isTuiBusyActivityStatus('starting up') = false (so renderStatus never enters the loader branch during init)

PRIOR_PR_HEAD_BEFORE (ee06d5cbe, before the gate fix)
onConnected reconnect w/ active run -> "starting up" overwrote reconnect-owned "streaming" (active run hidden)
post-init idle clear after loadHistory adoption -> overwrote adopted "streaming" with "idle" (in-flight run hidden)

Tests and validation

Commands run locally (Windows, Node v22.17.1):

  • pnpm tsgo — passed (TypeScript strict, no errors).
  • pnpm lint — passed (oxlint, full shard run).
  • pnpm format:check — the three changed files are clean (the 55 unrelated format failures pre-exist on main and are an environment artifact of Node 22.17 vs the required 22.19; not introduced here).
  • pnpm exec vitest run src/tui/tui.test.ts -t "isTuiBusyActivityStatus" — the new unit assertion fails before the fix (expected false to be true) and passes after.
  • pnpm exec vitest run src/tui/tui-command-handlers.test.ts src/tui/tui-event-handlers.test.ts src/tui/tui-session-actions.test.ts — 171 passed, no regressions.
  • pnpm build — core compilation (tsdown) succeeded; the post-build write-cli-startup-metadata step fails only because this machine runs Node 22.17.1 (< required 22.19.0) — unrelated to this change; CI runs Node 22.19+.
  • pnpm check:host-env-policy:swift — skipped on Windows (no Swift toolchain).

Regression coverage added:

  • src/tui/tui.test.ts — unit assertion isTuiBusyActivityStatus("starting up") === true (deterministic, fails before / passes after).
  • src/tui/tui-pty-harness.e2e.test.ts — PTY behavioral test that drives the real runTui/onConnected path with a slow post-connect init and asserts the busy "starting up" loader renders before "local ready" (runs in the Linux PTY CI shard; not reliably bootable under tsx+PTY on Windows).

What failed before this fix: isTuiBusyActivityStatus("starting up") returned false, so renderStatus() took the static-text branch and no loader rendered during onConnected init.

Risk checklist

Did user-visible behavior change? Yes — intentionally. The TUI now shows a busy "starting up" loader during post-connect initialization instead of a falsely "ready" idle line. The final ready state is unchanged.

Did config, environment, or migration behavior change? No.

Did security, auth, secrets, network, or tool execution behavior change? No. No CODEOWNERS secops paths touched.

What is the highest-risk area? The client.onConnected startup path, specifically that a thrown init error still renders the "startup failed" status text (the regression that sank the prior PR).

How is that risk mitigated? The success path clears "starting up" to "idle" only when no active run owns the status line — loadHistory() returning an adopted in-flight run, or a reconnect-owned streaming/running/finishing context status, is left intact so active work is not hidden. The .catch startup-failure path still clears to "idle" before setConnectionStatus("startup failed", 5000) so renderStatus() takes the static-text branch for failure/ready states. The "starting up" claim itself is gated by resolveStartupActivityStatus so it never overwrites reconnect-owned streaming. The post-init ready status string and TTL are unchanged.

Current review state

Next action: CI (including the Linux PTY shard) and maintainer review.

Waiting on: Linux CI to run the new PTY behavioral regression (not bootable on the contributor's Windows machine); maintainer review of the onConnected busy-state clear-on-failure invariant.

Bot/reviewer comments addressed: none yet (fresh PR).

@openclaw-barnacle openclaw-barnacle Bot added size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 17, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 12:15 AM ET / 04:15 UTC.

Summary
Adds starting up to the TUI busy activity set, claims it during post-connect initialization only when no busy run owns the status line, and adds unit plus PTY coverage.

PR surface: Source +12, Tests +24. Total +36 across 3 files.

Reproducibility: yes. by source inspection: current main and v2026.6.11 set local ready or connected before awaited post-connect initialization while the busy-status set excludes startup initialization. I did not run a live interactive Hatch session in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #74385
Summary: This PR is the active candidate fix for the canonical Terminal Hatch missing-startup-loader issue; earlier loader work was closed unmerged, and nearby Hatch timeout work covered a different stuck-Hatch mode.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • [P2] No repair lane is needed; the maintainer action is to wait for exact-head CI and normal merge checks on 5ec2bb19ff5df5f83183ef18fcbb49cac01f6d4c, then land or close the PR.

Security
Cleared: Cleared: the diff only changes TUI status logic and tests, with no dependency, workflow, secret, package, download, or code-execution surface change.

Review details

Best possible solution:

Land the narrow TUI startup-loader fix after exact-head checks pass, then let #74385 close through the merged PR.

Do we have a high-confidence way to reproduce the issue?

Yes, by source inspection: current main and v2026.6.11 set local ready or connected before awaited post-connect initialization while the busy-status set excludes startup initialization. I did not run a live interactive Hatch session in this read-only review.

Is this the best way to solve the issue?

Yes. Reusing the existing busy-loader path and clearing only the exact startup-owned state is the narrow maintainable fix; it avoids widening loadHistory() and preserves reconnect/in-flight streaming states.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 37f96bde4d90.

Label changes

Label justifications:

  • P2: This is a normal-priority TUI onboarding responsiveness fix with limited blast radius and no data-loss, security, or availability emergency.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Sufficient: the PR body and maintainer refresh include terminal PTY output plus Blacksmith Testbox proof showing starting up before local ready | idle on the changed TUI path.
  • proof: sufficient: Contributor real behavior proof is sufficient. Sufficient: the PR body and maintainer refresh include terminal PTY output plus Blacksmith Testbox proof showing starting up before local ready | idle on the changed TUI path.
Evidence reviewed

PR surface:

Source +12, Tests +24. Total +36 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 12 0 +12
Tests 2 24 0 +24
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 36 0 +36

What I checked:

  • PR head busy status: At the reviewed head, starting up is included in TUI_BUSY_ACTIVITY_STATUSES, so the existing status renderer treats startup initialization as loader-worthy. (src/tui/tui.ts:422, 5ec2bb19ff5d)
  • PR head startup ownership rule: client.onConnected sets starting up only when the current activity is not busy, then clears only the exact starting up state on success or startup failure. (src/tui/tui.ts:1576, 5ec2bb19ff5d)
  • Renderer contract: renderStatus() enters the loader path only when isTuiBusyActivityStatus(activityStatus) returns true, so the fix uses the existing owner boundary rather than adding a parallel renderer path. (src/tui/tui.ts:1119, 5ec2bb19ff5d)
  • Sibling in-flight run path: loadHistory() can adopt an in-flight run and set activity to streaming; the PR's exact-state clear avoids clobbering that sibling path. (src/tui/tui-session-actions.ts:532, 37f96bde4d90)
  • Current main still lacks the fix: Current main has the busy-status set and ready-before-init sequence but no starting up entry or startup activity claim, so this PR is still necessary. (src/tui/tui.ts:422, 37f96bde4d90)
  • Latest release still lacks the fix: v2026.6.11 contains the same busy-status set and ready-before-init sequence without startup activity, so the behavior has not shipped yet. (src/tui/tui.ts:419, e085fa1a3ffd)

Likely related people:

  • steipete: Live PR metadata lists steipete as assignee, and the current head commit is a maintainer refresh of the startup-loader fix with proof. (role: recent PR refresh author and assignee; confidence: high; commits: 5ec2bb19ff5d; files: src/tui/tui.ts, src/tui/tui.test.ts, src/tui/tui-pty-harness.e2e.test.ts)
  • fuller-stack-dev: Commit 276c000 introduced local embedded TUI mode and the onboarding Hatch flow that reaches this startup path. (role: introduced behavior; confidence: high; commits: 276c00015c83; files: src/tui/tui.ts, src/tui/tui-session-actions.ts, src/wizard/setup.finalize.ts)
  • rogerdigital: Commit d4e52f4 changed reconnect streaming-watchdog behavior in the same connection/activity-status area this PR must preserve. (role: recent adjacent TUI contributor; confidence: medium; commits: d4e52f454255; files: src/tui/tui.ts, src/tui/tui-event-handlers.ts, src/tui/tui-event-handlers.test.ts)
  • joshavant: Commit 86cc292 added bounded Hatch timeout handling for a related first-run Terminal Hatch stuck-flow. (role: recent adjacent hatch contributor; confidence: medium; commits: 86cc29274e03; files: src/wizard/setup.finalize.ts, src/tui/tui-launch.test.ts, src/tui/embedded-backend.test.ts)
  • shakkernerd: Commit aae4b1b changed setup-to-TUI terminal handoff behavior adjacent to the reported onboarding path. (role: recent hatch handoff contributor; confidence: medium; commits: aae4b1b29dd7; files: src/tui/tui-launch.ts, src/wizard/setup.finalize.ts, src/wizard/setup.finalize.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-01T07:23:29.873Z sha 0a5b6eb :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-06T02:36:42.480Z sha bd74897 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T03:27:44.593Z sha 7784435 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 19, 2026
@ml12580

ml12580 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the correctness concern from the last review: the post-connect onConnected path unconditionally cleared the activity status to idle right after loadHistory(), which clobbered the streaming status loadHistory() sets when it adopts a gateway-reported in-flight run — hiding the resumed run's work (the same freeze symptom this loader fix targets).

Changes:

  • loadHistory() now returns Promise<boolean>true when it adopted an in-flight run and set streaming.
  • onConnected only clears the startup starting up status to idle when loadHistory() did not adopt an in-flight run; otherwise the resumed run keeps its streaming status.
  • Widened the loadHistory type in the tui-command-handlers / tui-event-handlers consumer interfaces to match.
  • Added a regression test asserting loadHistory() returns true on adoption and false when no in-flight run exists.

Verified: tsgo clean, scoped oxlint clean, tui-session-actions (34) + tui-command-handlers/tui-event-handlers/tui (201) tests pass.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@ml12580

ml12580 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the remaining review finding (Preserve reconnect-owned streaming before startup — src/tui/tui.ts:1548):

reconnectStreamingWatchdog() can restore streaming for a still-active run, but onConnected then unconditionally overwrote it with starting up. The startup status is now gated:

  • New resolveStartupActivityStatus(currentStatus) claims starting up only when no active-run status (streaming/running/finishing context) owns the line; otherwise it preserves the existing status. onConnected uses it for the initial set.
  • The post-init idle clear is now gated the same way, so a reconnect-owned active-run status (or an in-flight run adopted by loadHistory(), which sets streaming) is not clobbered.
  • Added isTuiActiveRunActivityStatus + resolveStartupActivityStatus helpers and reconnect regression tests (resolveStartupActivityStatus preserves streaming/running/finishing context).

Verified: tsgo + tsgo:core:test clean, scoped oxlint clean, tui/tui-session-actions/tui-command-handlers tests pass (159). Updated the PR body's Real behavior proof to exercise the real exported helpers and document the reconnect-preservation behavior.

On the real-interactive-proof ask: the TUI requires an interactive TTY and won't boot headless on Windows (Crestodian needs an interactive TTY); the PTY harness only runs reliably in the Linux CI shard with a fake backend. A real interactive TUI/Hatch recording needs a real backend on hardware — per the review, a maintainer can capture it with @openclaw-mantis visual task: capture OpenClaw TUI during slow post-connect initialization and verify the starting up loader appears without hiding active streaming after reconnect.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@ml12580

ml12580 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Added real-render PTY evidence to the PR body (addressing the "helper terminal output" gap):

Captured the ACTUAL rendered terminal bytes from the production runTui()/onConnected/renderStatus()/Loader path under a real Linux PTY (Node v22.22.0, ssh lxy-tx), via src/tui/tui-pty-harness.e2e.test.ts "shows a busy startup loader during post-connect initialization" with OPENCLAW_TUI_PTY_MIRROR_PATH set to mirror the raw PTY output. A slow post-connect init fixture makes the loader window visible (the scenario issue #74385 reports).

AFTER (PR head 0a5b6eb) — real rendered status line:

starting up • 0s | starting local runtime   <- busy loader renders during post-connect init
local ready | idle                          <- static ready line once init completes

BEFORE (upstream main) — real rendered status line, same harness:

"starting up" busy-loader frames during init: 0
local ready | idle                          <- no loader; init shows no spinner (looks frozen)

So on main the loader never renders during init (0 "starting up" frames); on the PR head the busy "starting up" loader renders during post-connect init then clears to the unchanged "local ready | idle" ready line. The reconnect-owned-streaming preservation half is covered by the real exported resolveStartupActivityStatus (preserves streaming/running/finishing context) plus the resolveStartupActivityStatus unit tests.

Honest scope: the render path driven is production code under a real PTY, but the harness backend is a test fixture (not a live gateway/embedded runtime), and a slow-init fixture makes the loader window visible. A real-backend interactive Terminal Hatch recording on physical hardware is still outstanding — a maintainer can capture it via @openclaw-mantis visual task: capture OpenClaw TUI during slow post-connect initialization and verify the starting up loader appears without hiding active streaming after reconnect.

@clawsweeper re-review

@ml12580

ml12580 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Strengthened the proof with a REAL embedded-backend local runtime capture (not the fake-backend harness):

Ran src/tui/tui-pty-local.e2e.test.ts on Linux (Node v22.22.0) — this drives the REAL local embedded runtime under a real PTY, with only the upstream model HTTP endpoint mocked (the embedded runtime / onConnected / session / loadHistory path is real). It boots, post-connect init completes, the status line reaches local ready | idle, and a full chat round-trip returns LOCAL_PTY_RESPONSE:

local ready | idle
(user) send the local PTY smoke response
(assistant) LOCAL_PTY_RESPONSE

So the real local runtime/session path is exercised end-to-end (this is not the fake-backend harness barred by src/tui/AGENTS.md). The "starting up" busy loader itself only paints when post-connect init is slow; on a fresh workspace the real init is fast, so the loader's paint is shown via the slow-init fixture (real runTui/renderStatus/Loader render path) in the PR body:

starting up • 0s | starting local runtime   <- busy loader during slow post-connect init
local ready | idle

and on upstream main the same harness renders 0 "starting up" frames (no loader — the reported freeze).

The remaining gap is a real interactive Terminal Hatch run with naturally slow first-run init on physical hardware, which the contributor cannot capture (Windows host cannot boot the TUI headless). That can be captured via @openclaw-mantis visual task: capture OpenClaw TUI during slow post-connect initialization and verify the starting up loader appears without hiding active streaming after reconnect.

@clawsweeper re-review

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 24, 2026
@ml12580

ml12580 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@steipete @vincentkoc — requesting a maintainer Mantis capture for the one piece of proof I could not produce as an external contributor.

What the fix does (bot-endorsed): adds a starting up busy activity status so the TUI shows the loader during post-connect init instead of looking frozen; gates it so it never overwrites a reconnect-owned streaming/running/finishing context status, and so the post-init idle clear doesn't clobber an in-flight run adopted by loadHistory(). ClawSweeper rates patch 🐚 platinum (the source blocker is resolved) — the only holdup is proof.

The proof I could and couldn't capture (all on Linux, real runTui/renderStatus/Loader):

Captured (in the PR body):

  1. Real-PTY render capture via tui-pty-harness.e2e.test.ts slow-init fixture, with OPENCLAW_TUI_PTY_MIRROR_PATH mirroring the actual rendered terminal bytes. AFTER (PR head): starting up • 0s | starting local runtimelocal ready | idle. BEFORE (main): 0 starting up frames, jumps straight to local ready | idle (no loader — the reported freeze).
  2. Real embedded-backend local-runtime run via tui-pty-local.e2e.test.ts (only the upstream model HTTP endpoint mocked; the embedded runtime / onConnected / session / loadHistory path is real): boots, reaches local ready | idle, full chat round-trip returns LOCAL_PTY_RESPONSE.

Could NOT capture (the gap):

  • The starting up loader painting during a naturally slow real-backend init. Per src/tui/AGENTS.md, the fake-backend slow-init fixture (item 1) must not be claimed as proof of the embedded runtime itself, so it shows the loader render but not against a real backend.
  • I tried to make the real embedded-backend init slow enough for the loader to paint, on a Linux box: flipped skipBootstrap: true→false, raised LOCAL_STARTUP_TIMEOUT_MS (20s→120s) + LOCAL_TEST_TIMEOUT_MS (150s→300s), and added a 2.5s delay to the mock /v1/models endpoint (all temporary, reverted). None painted the loader — the real local onConnected init completes faster than the first render tick, so setActivityStatus("starting up") is set and cleared before any frame renders; the capture contains 0 starting up frames and goes straight to local ready | idle.
  • The loader is only visible when post-connect init is genuinely multi-second slow, which happens in the real heavy first-run Terminal Hatch environment (model discovery, agent bootstrap, large history restore) — not reproducible on a fresh-temp-workspace Linux box, and the TUI requires an interactive TTY so it won't boot headless on my Windows host (Crestodian needs an interactive TTY).

So the one missing artifact is a real interactive Terminal Hatch run with naturally slow first-run init showing the starting up loader — which needs real Hatch hardware a contributor can't easily provide.

ClawSweeper itself flags this as the sole remaining rank-up move and suggests exactly this capture. Could a maintainer run it?

@openclaw-mantis visual task: capture OpenClaw TUI during slow post-connect initialization and verify the starting up loader appears without hiding active streaming after reconnect.

CI is fully green (65/65) on 0a5b6eb8f2. Thanks for looking.

@clawsweeper clawsweeper Bot added status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. labels Jun 26, 2026
@steipete steipete self-assigned this Jul 6, 2026
@steipete
steipete force-pushed the fix/vuln-74385-tui-startup-loader branch from 0a5b6eb to bd74897 Compare July 6, 2026 02:19
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Maintainer refresh complete at bd748975324.

What changed:

  • replaced the stale multi-helper patch with one ownership rule on current main: startup claims the activity line only when no busy run owns it, and clears only the exact starting up state it created;
  • preserved reconnect-restored streaming/running states without widening the loadHistory API;
  • added focused status coverage and a real PTY test with deliberately delayed post-connect initialization;
  • preserved contributor credit in the replacement commit.

Verification:

  • node scripts/run-vitest.mjs src/tui/tui.test.ts — 66 tests passed
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts — 18 PTY scenarios passed
  • pnpm check:changed -- src/tui/tui.ts src/tui/tui.test.ts src/tui/tui-pty-harness.e2e.test.ts
  • fresh branch autoreview: no accepted/actionable findings

Remote proof: Blacksmith Testbox through Crabbox, tbx_01kwtkghw4hd4pxzwt7esdrc4c (tidal-shrimp), Actions run https://github.com/openclaw/openclaw/actions/runs/28763626933. The PTY visibly showed starting up before local ready | idle.

Thanks @ml12580 for the original diagnosis and fix direction.

Exact-head land-ready update: 5ec2bb1 is the clean current-main-plus-reviewed-patch head. Hosted CI run https://github.com/openclaw/openclaw/actions/runs/28767169467 passed at that exact SHA. The rebase changed ancestry only; the tested diff and Testbox/browser/PTY proof above are unchanged. Known proof gaps: none.

@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

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

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Stuck and ultra slow during Hatch (terminal mode) - took a minute to come back up

2 participants