Summary
On a fresh openclaw tui --session <key> session, sending any message produces a spinner that spins forever. Pressing Esc prints no active run. No assistant reply ever appears in the TUI — yet the backend does run the turn and writes the reply to the session file on disk. Running the same command with --deliver works instantly.
This is a superset of #33102. That (stale) issue covers the ergonomic half — "let me set a default for --deliver in config". This issue is about the harder UX failure: when a user hits today's default, the TUI's run-lifecycle state machine cannot recover, and the product looks broken.
Reproduction
openclaw tui --session smoke-2
- Type
2+2
- Observed: spinner shows
bamboozling... | connected indefinitely.
- Press Esc →
no active run.
- No reply is ever rendered.
openclaw tui --deliver --session smoke-2 → immediately renders 4.
- Session file on disk contains the reply — backend answered, it just never streamed to this client.
Confirmed on OpenClaw 2026.4.14 (commit 323493f), macOS (Homebrew), zsh.
Why it's not "just a default"
In dist/tui-*.js:
sendMessage sets activityStatus = "waiting" right after client.sendChat(...) resolves.
state.activeChatRunId is only set inside handleChatEvent — i.e. when a chat.event frame arrives from the gateway.
activityStatus is only cleared inside finalizeRun / lifecycle-end handlers, which also depend on chat.event frames.
When deliver: false is sent on chat.send, the gateway runs the turn to completion but does not stream chat.event frames back to this client. As a result:
activeChatRunId stays null forever → spinner never stops.
abortActive() sees activeChatRunId == null → prints no active run on Esc.
- The user's mental model lands on "the TUI is broken", not "the reply was stored but not streamed to me".
And because --deliver defaults to false in cli/tui-cli.ts, every new user hits this on their very first session.
Proposed fix
Four changes:
- Flip
--deliver default to true in the TUI CLI. Add --no-deliver as the explicit opt-out. (Per commander.js docs: if --deliver is declared first, adding --no-deliver does not change the default — so --deliver remains a harmless no-op for anyone with muscle memory.)
- Make
deliverDefault mutable at runtime and expose it via state (getter/setter) so a settings toggle can flip it.
- In
sendMessage, when delivery is off, don't hang — clear pendingOptimisticUserMessage, log a clear system line, and set activityStatus back to idle instead of "waiting".
- Add a persistent Deliver replies row to the settings panel, next to Tool output / Show thinking.
Local patch / workaround
I've written a runnable patch script that applies all four changes to an installed 2026.4.14 bundle, along with the full root-cause write-up:
https://github.com/arcabotai/openclaw-tui-deliver-stuck-spinner
git clone https://github.com/arcabotai/openclaw-tui-deliver-stuck-spinner
cd openclaw-tui-deliver-stuck-spinner
node apply-local-patch.mjs
It's idempotent, verifies every old string is present before touching anything, and aborts cleanly on a version mismatch. Happy to open a source-tree PR against this repo if a maintainer wants to pull it in directly.
Environment
- OpenClaw 2026.4.14 (
323493f)
- macOS (Homebrew), zsh
- Install path:
/opt/homebrew/lib/node_modules/openclaw
Reported by arcabot.ai.
Summary
On a fresh
openclaw tui --session <key>session, sending any message produces a spinner that spins forever. Pressing Esc printsno active run. No assistant reply ever appears in the TUI — yet the backend does run the turn and writes the reply to the session file on disk. Running the same command with--deliverworks instantly.This is a superset of #33102. That (stale) issue covers the ergonomic half — "let me set a default for
--deliverin config". This issue is about the harder UX failure: when a user hits today's default, the TUI's run-lifecycle state machine cannot recover, and the product looks broken.Reproduction
openclaw tui --session smoke-22+2bamboozling... | connectedindefinitely.no active run.openclaw tui --deliver --session smoke-2→ immediately renders4.Confirmed on OpenClaw 2026.4.14 (commit
323493f), macOS (Homebrew), zsh.Why it's not "just a default"
In
dist/tui-*.js:sendMessagesetsactivityStatus = "waiting"right afterclient.sendChat(...)resolves.state.activeChatRunIdis only set insidehandleChatEvent— i.e. when achat.eventframe arrives from the gateway.activityStatusis only cleared insidefinalizeRun/ lifecycle-end handlers, which also depend onchat.eventframes.When
deliver: falseis sent onchat.send, the gateway runs the turn to completion but does not streamchat.eventframes back to this client. As a result:activeChatRunIdstaysnullforever → spinner never stops.abortActive()seesactiveChatRunId == null→ printsno active runon Esc.And because
--deliverdefaults tofalseincli/tui-cli.ts, every new user hits this on their very first session.Proposed fix
Four changes:
--deliverdefault totruein the TUI CLI. Add--no-deliveras the explicit opt-out. (Per commander.js docs: if--deliveris declared first, adding--no-deliverdoes not change the default — so--deliverremains a harmless no-op for anyone with muscle memory.)deliverDefaultmutable at runtime and expose it viastate(getter/setter) so a settings toggle can flip it.sendMessage, when delivery is off, don't hang — clearpendingOptimisticUserMessage, log a clear system line, and setactivityStatusback toidleinstead of"waiting".Local patch / workaround
I've written a runnable patch script that applies all four changes to an installed 2026.4.14 bundle, along with the full root-cause write-up:
https://github.com/arcabotai/openclaw-tui-deliver-stuck-spinner
It's idempotent, verifies every old string is present before touching anything, and aborts cleanly on a version mismatch. Happy to open a source-tree PR against this repo if a maintainer wants to pull it in directly.
Environment
323493f)/opt/homebrew/lib/node_modules/openclawReported by arcabot.ai.