anomalyco / anomalyco/opencode

[Bug]: TUI does not render externally injected (steer) turns or the assistant's reply while attached — data intact in DB (v1.18.26)

Open
#47,622 0 comments 1 reaction 1 assignee View on GitHub

@kommander is already working on this.

Since Sep 6, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
Avg merge
7h 2m
Merged PRs (30d)
384

Description

Description

While the TUI is attached, turns injected externally (steer) into an idle session — in this case a <system-reminder> user message delivered when a background subagent completes — and the assistant's reply to them are not rendered in the TUI. The conversation view jumps from the last locally-typed message straight to the next locally-typed message. Locally-typed turns render normally.

The data layer is fully intact: both the injected user turn and the assistant reply are persisted in the DB with correct content and no hiding flags, and the server log shows the wake-up run executed cleanly (INFO only, no errors).

This is a fresh, cleanly-evidenced instance of the symptom class from #41289 (closed by the reporter as "less consistent than reported" — see also #8564, #26671, candidate fix PR #26936 which was never confirmed). Reproduced twice in a row on v1.18.26.

Plugins

oh-my-opencode (omo). The injection is performed by omo's background-task completion notification (same steer/prompt_async path as #41289's pure-HTTP repro). Note the rendering gap itself is in the TUI's handling of externally injected turns, independent of which client performs the injection — #41289 reproduced it with plain curl.

OpenCode version

v1.18.26 (homebrew, current latest stable)

Steps to reproduce
  1. Open the TUI in a session, let it go idle (agent finished its turn).
  2. Have the agent launch a background subagent that completes a few seconds later (omo task(..., run_in_background=true)).
  3. The plugin injects a <system-reminder> user message into the parent session via steer; the server wakes the session, the model replies (verified in DB).
  4. Watch the TUI while 2–3 run: neither the injected turn nor the assistant's reply appears. The view stays on the last locally-typed exchange.
  5. Type any message locally: it and its reply render normally, after the invisible gap.

Evidence (all timestamps UTC, session ses_f89f80d79ffejhqzqNImj9GU2w):

Timeline (from ~/.local/share/opencode/log/opencode.log and opencode.db):

Time Event
09:27:02.893 child session created (explore subagent, parentID = this session)
09:27:02.905 child replies (PONG-2)
09:27:08 reminder #1 persisted as user msg msg_0760b02ab001c5AXKMgrmSbCxq
09:27:10.538 reminder #2 (identical duplicate) persisted as user msg msg_0760b0bca0019uwqYxxrHUya98
09:27:10.543 wake-up run starts (run=50a553ac, distinct from the interactive run)
09:27:10.545 assistant msg msg_0760b0bd1001rQIda5zOKKXHMq created (parent = reminder #2), contains the text reply to the notification
09:27:16 / 09:27:22 follow-up assistant msgs (tool calls + final summary)
09:27:27.839 run exits loop normally

Log excerpt (wake-up run, INFO only throughout):

timestamp=2026-09-06T09:27:10.543Z level=INFO run=50a553ac message=loop session.id=ses_f89f80d79ffejhqzqNImj9GU2w step=0
timestamp=2026-09-06T09:27:10.564Z level=INFO run=50a553ac message=process session.id=ses_f89f80d79ffejhqzqNImj9GU2w messageID=msg_0760b0bd1001rQIda5zOKKXHMq
timestamp=2026-09-06T09:27:10.565Z level=INFO run=50a553ac message=stream providerID=zhipuai-coding-plan modelID=glm-5.3 ...
timestamp=2026-09-06T09:27:27.839Z level=INFO run=50a553ac message="exiting loop" session.id=ses_f89f80d79ffejhqzqNImj9GU2w step=3

DB proof (sqlite, message + part tables):

-- both injected reminders persisted, with full text:
msg_0760b02ab001c5AXKMgrmSbCxq | {"type":"text","text":"<system-reminder>\n[BACKGROUND TASK COMPLETED]\n[ALL BACKGROUND TASKS COMPLETE]\n\n**Completed:**\n- `bg_3f428c9d`: Notificatio…
msg_0760b0bca0019uwqYxxrHUya98 | (identical duplicate, 2.4s later)

-- the invisible assistant reply persisted, normal flags, no synthetic/ignored:
msg_0760b0bd1001rQIda5zOKKXHMq | {"type":"text","text":"📢 **显式确认:我已收到后台任务完成通知。**\n\n- 收到时间:…
-- message row: role=assistant, parentID=msg_0760b0bca…, finish/tool-calls, tokens counted

Neither message carries synthetic or ignored flags, so this is not the flag-based skip — the TUI simply never rendered these turns while attached.

Two additional observations from the same repro that may complicate the render path:

  • Duplicate injection: the reminder was injected twice (two persisted user messages 2.4s apart). In an earlier round of the same test the reminder was not persisted at all (assistant wake-up reply exists at 09:24:13 with no preceding user row) — persistence on this path is inconsistent.
  • A background-notification wake during an active parent turn is a separate known timing class (see omo repo issues); this report is strictly about the attached-TUI rendering gap.
Screenshot and/or share link

Available on request (contains a private session; happy to share/redact). DB message IDs above are verbatim for correlation.

Operating System

macOS 26.5 (arm64)

Terminal

ghostty 1.x, direct attach (no tmux/zellij), TERM=xterm-256color

Expected behavior

Injected turns and the assistant's reply render in the attached TUI live (or on the next event batch), exactly like locally-typed turns. At minimum, a re-render/backfill should cover them; per #41289's reporter, a freshly attached TUI does render the assistant reply, but the injected user turn still never appears — suggesting the TUI depends entirely on live SSE delivery for these turns with no hydration fallback.

Actual behavior

The injected user turn and the assistant's reply never render while attached; the human user only discovers the gap when comparing the DB/API against the screen. Messages typed locally before/after render fine.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.