You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tui: background shell completion notices can accumulate below the active turn #63345
Intermittently, in the TUI, a finished background shell's Shell finished · <command> notice is appended after the active turn instead of folding into its tool row. When several of them land while the turn is still running, they stack below the Thinking/assistant row, the active turn is no longer the latest message, and the viewport follows the newest notice instead of the live output.
It does not happen on every turn that uses background shells; it appears randomly. I have not identified the exact trigger.
Frequency: intermittent (random); not consistently reproducible.
Plugins
alerta.ts, github-write-gate.ts, opencode-todo
OpenCode version
2.2.7
Steps to reproduce
Use the TUI (WSL2) and send prompts that make the agent launch several shell commands with background: true while it keeps working.
Repeat a few turns: the bug is intermittent; most turns do not reproduce it.
When it happens, the Shell finished notices appear below the active Thinking/assistant row and keep accumulating as more background shells finish.
Screenshot
Operating System
Linux 6.18.33.2-microsoft-standard-WSL2 (WSL2, x86_64)
Thanks for the detailed report! I tried this on 2.2.7 with a simulated model: the agent starts three background shells, then runs one long step while they all finish.
I saw the same picture as your screenshot: while the step was still running, the Shell finished rows stacked below the Thinking row. As far as I can tell, this is how the TUI is designed to work, not a bug:
A finished background shell's notice is queued for the agent and only handed to it at the start of its next step.
Until then, the TUI shows it at the bottom as pending input, the same way it shows a message you send while the agent is busy.
When the step ended, the notices moved into place after that step's output, and the agent's next step received them and continued. Nothing stayed stuck at the bottom.
That's also why it seems random: it only happens when shells finish in the middle of a long step. Showing the notice inside its shell tool row isn't something the TUI does today; notices are always separate rows. If you'd like them drawn differently while they're pending (for example, never below the live turn), please open a feature request. #61732 is related.
If you see notices that stay below the turn after the agent's next step has started, or the live output gets pushed off screen while it streams, please reply with a screenshot or recording of that and, if you can, your session settings in the TUI config. That would be a real bug and we'd look again.
Confirmed: the older Shell finished rows accumulate and stay pinned at the bottom. There are already several of them from previous commands. When I send a new message, it appears at the bottom first, then the message and its answer move above those rows, and the old Shell finished rows stay at the bottom. In the recording: I start two background shells (sleep 5, sleep 7) and the agent answers the ocean question; then I send "Quick one: what's the capital of US? One word." and get "Washington"; both messages and answers end up above the old Shell finished stack, which remains below. The session inbox was empty and the session was idle, so those notices were delivered, not pending.
The recording shows that sequence; the old command text is redacted. TUI settings: default — no cli.json; no custom session, tabs, scroll or keybinds settings. Version 2.2.7 on WSL2 / Windows Terminal.
Thanks for the recording and the extra detail. That helps a lot: notices that stay pinned after they've been delivered, with an idle session, are not the designed behaviour I described before.
I tried your sequence on 2.2.7 with default TUI settings and no plugins:
The agent starts sleep 5 and sleep 7 in the background, answers a question and ends its turn.
Both shells finish while the session is idle. Each Shell finished row appears and the agent picks it up.
I send "Quick one: what's the capital of US? One word." and get "Washington".
In my run, the new message and its answer showed up after the Shell finished rows, in time order. Nothing stayed pinned at the bottom, so I couldn't reproduce it yet.
The main differences from your setup are your plugins and a longer session. These would help narrow it down:
Plugins: does it still happen with alerta.ts, github-write-gate.ts and opencode-todo disabled, in a fresh session?
Reconnects: before the rows got stuck, did the TUI reconnect, or did the OpenCode service restart? WSL2 sleeping or the terminal being closed could cause this.
Restart: if you quit the TUI and reopen the same session, are the old Shell finished rows still at the bottom, or back in place?
Fresh session: can you get the stuck rows with the short sleep 5 / sleep 7 sequence alone, starting from a new session?
Results (it has happened only 2–3 times, randomly):
Reopening the same session: no — the transcript is back in the correct order, with nothing stuck at the bottom. So the stuck rows are not persisted; a TUI restart fixes them.
Fresh session with the short sleep 5 / sleep 7 sequence: no, I couldn't reproduce it.
I skipped the long-session-with-plugins-disabled test because it would spend model credit; I can't justify that for a random issue.
Important: this is not tied to a restart or reconnect. The TUI had been running normally and the session had been working; the rows started sticking mid-session, with no interruption at that moment. It has happened 2–3 times, randomly, always in long sessions. Reopening the session puts the rows back in place, so it looks like a live TUI client state rather than the persisted transcript order.
Thanks for testing those, especially the reopen result. Since reopening fixes the order, the saved session is fine and the problem is in what the running TUI keeps in memory.
I tried again with a long session on 2.2.7, using a simulated model and default settings: 12 turns, each starting two background shells, with steer messages sent mid-step and pauses where the shells finished while the session was idle. Every Shell finished row went into the right place, and later messages and answers showed up after them. I couldn't get them to stick.
What you describe (new messages show up at the bottom first and then jump above the stuck rows) is how the TUI places input it thinks hasn't been delivered yet. My best guess is that the TUI sometimes keeps treating an already-delivered notice as pending. I haven't confirmed that and I don't know what triggers it, so I'll leave it for the maintainers to look at in the code.
Until then, reopening the session puts the rows back in order. If it happens again, these would help:
A note of what was going on just before the first row got stuck. For example, whether you sent a message while the agent was working, or a shell finished right as a turn ended.
Whether one of your plugins sends messages into the session, or reacts to shell results.
Thanks again for the recording and for testing the reopen and fresh-session cases. I tried three times on 2.2.7 to get the Shell finished rows to stick below newer messages: notices arriving during a long step, your sleep 5 / sleep 7 sequence, and a long session of many turns with mid-step steer messages and idle gaps. They went into the right place every time. Without a way to trigger it, I'm closing this issue for now.
Until it's tracked down, reopening the session puts the rows back in order. If it happens again, please share what was going on just before the first row got stuck (for example, a message sent while the agent was working, or a shell finishing right as a turn ended), whether any of your plugins send messages into the session, and the TUI log from around that time. Replying here reopens the issue.
Added a deterministic v2.2.7 client-state reproduction and runnable test at #59615 (comment) . A stale inbox hydration response arriving after fresh transcript hydration reintroduces consumed user/synthetic input as pending, and the unchanged TUI reducer moves it below the assistant. Four race cases fail; eight ordering/delivery controls pass. No plugins are loaded in this fixture. This reproduces a client race consistent with the stuck-row symptom; the original live event sequence remains uncaptured.
Activity
opencodejr commentedon Nov 11, 2026
Thanks for the detailed report! I tried this on 2.2.7 with a simulated model: the agent starts three background shells, then runs one long step while they all finish.
I saw the same picture as your screenshot: while the step was still running, the
Shell finishedrows stacked below theThinkingrow. As far as I can tell, this is how the TUI is designed to work, not a bug:That's also why it seems random: it only happens when shells finish in the middle of a long step. Showing the notice inside its shell tool row isn't something the TUI does today; notices are always separate rows. If you'd like them drawn differently while they're pending (for example, never below the live turn), please open a feature request. #61732 is related.
If you see notices that stay below the turn after the agent's next step has started, or the live output gets pushed off screen while it streams, please reply with a screenshot or recording of that and, if you can, your
sessionsettings in the TUI config. That would be a real bug and we'd look again.jarmenterosx2 commentedon Nov 11, 2026
Confirmed: the older
Shell finishedrows accumulate and stay pinned at the bottom. There are already several of them from previous commands. When I send a new message, it appears at the bottom first, then the message and its answer move above those rows, and the oldShell finishedrows stay at the bottom. In the recording: I start two background shells (sleep 5,sleep 7) and the agent answers the ocean question; then I send "Quick one: what's the capital of US? One word." and get "Washington"; both messages and answers end up above the oldShell finishedstack, which remains below. The session inbox was empty and the session was idle, so those notices were delivered, not pending.The recording shows that sequence; the old command text is redacted. TUI settings: default — no cli.json; no custom session, tabs, scroll or keybinds settings. Version 2.2.7 on WSL2 / Windows Terminal.
Screen recording:
https://github.com/user-attachments/assets/a8d9e348-7c65-4403-a596-eea017be470c
opencodejr commentedon Nov 11, 2026
Thanks for the recording and the extra detail. That helps a lot: notices that stay pinned after they've been delivered, with an idle session, are not the designed behaviour I described before.
I tried your sequence on 2.2.7 with default TUI settings and no plugins:
sleep 5andsleep 7in the background, answers a question and ends its turn.Shell finishedrow appears and the agent picks it up.In my run, the new message and its answer showed up after the
Shell finishedrows, in time order. Nothing stayed pinned at the bottom, so I couldn't reproduce it yet.The main differences from your setup are your plugins and a longer session. These would help narrow it down:
alerta.ts,github-write-gate.tsandopencode-tododisabled, in a fresh session?Shell finishedrows still at the bottom, or back in place?sleep 5/sleep 7sequence alone, starting from a new session?jarmenterosx2 commentedon Nov 11, 2026
Results (it has happened only 2–3 times, randomly):
sleep 5/sleep 7sequence: no, I couldn't reproduce it.Important: this is not tied to a restart or reconnect. The TUI had been running normally and the session had been working; the rows started sticking mid-session, with no interruption at that moment. It has happened 2–3 times, randomly, always in long sessions. Reopening the session puts the rows back in place, so it looks like a live TUI client state rather than the persisted transcript order.
opencodejr commentedon Nov 11, 2026
Thanks for testing those, especially the reopen result. Since reopening fixes the order, the saved session is fine and the problem is in what the running TUI keeps in memory.
I tried again with a long session on 2.2.7, using a simulated model and default settings: 12 turns, each starting two background shells, with steer messages sent mid-step and pauses where the shells finished while the session was idle. Every
Shell finishedrow went into the right place, and later messages and answers showed up after them. I couldn't get them to stick.What you describe (new messages show up at the bottom first and then jump above the stuck rows) is how the TUI places input it thinks hasn't been delivered yet. My best guess is that the TUI sometimes keeps treating an already-delivered notice as pending. I haven't confirmed that and I don't know what triggers it, so I'll leave it for the maintainers to look at in the code.
Until then, reopening the session puts the rows back in order. If it happens again, these would help:
opencodejr commentedon Nov 12, 2026
Thanks again for the recording and for testing the reopen and fresh-session cases. I tried three times on 2.2.7 to get the
Shell finishedrows to stick below newer messages: notices arriving during a long step, yoursleep 5/sleep 7sequence, and a long session of many turns with mid-step steer messages and idle gaps. They went into the right place every time. Without a way to trigger it, I'm closing this issue for now.Until it's tracked down, reopening the session puts the rows back in order. If it happens again, please share what was going on just before the first row got stuck (for example, a message sent while the agent was working, or a shell finishing right as a turn ended), whether any of your plugins send messages into the session, and the TUI log from around that time. Replying here reopens the issue.
Neko-Yukari commentedon Nov 12, 2026
Added a deterministic v2.2.7 client-state reproduction and runnable test at #59615 (comment) . A stale inbox hydration response arriving after fresh transcript hydration reintroduces consumed user/synthetic input as pending, and the unchanged TUI reducer moves it below the assistant. Four race cases fail; eight ordering/delivery controls pass. No plugins are loaded in this fixture. This reproduces a client race consistent with the stuck-row symptom; the original live event sequence remains uncaptured.