Repository navigation
fix(web): keep a long turn visible across conversation switches and browser reopens - #28
Conversation
…rowser reopens - Tag every agent-turn frame (thinking, tool status, reply, error, trajectory) and busy frame with the turn's conversation ID. - Broadcast turn frames to all tabs instead of only the tab that sent the message, so a reopened browser still gets the turn's replies. - Replay the last busy frame and the running turn's frames to a tab when it connects, so it sees the progress so far. - UI: block switching conversations (sidebar, + New, Fork, typed /new|/load|/fork) while a turn is running or pending; hold incoming frames until the history has loaded; follow turns started from another tab; clear the pending dots when a message is rejected (e.g. during restart). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dwjz3bkBQygqJ7jmUd7Tb
There was a problem hiding this comment.
🟡 Changes recommended
Five unresolved moderate issues remain in frontend switch guards, replay deduplication, refusal handling, and trajectory delivery.
1 open issue
What changed in this PR
This pull request improves continuity for long-running agent turns across conversation switches, browser reconnects, and multiple tabs by associating turn activity with conversations and replaying in-progress frames.
Changes:
- Adds conversation identifiers to turn metadata, busy frames, errors, and trajectory notifications.
- Broadcasts and replays bounded in-progress turn activity across connected tabs.
- Prevents conversation switching during active or pending turns and buffers frames during history loading.
- Adds regression tests and documents the updated WebChannel behavior.
| File | Description |
|---|---|
tests/test_web_channel.py |
Tests frame broadcasting, replay, capping, and frontend locking behavior. |
tests/test_background_agent.py |
Tests conversation metadata propagation on turn responses and errors. |
CLAUDE.md |
Documents the updated WebChannel turn and replay behavior. |
app/core/background_agent.py |
Adds conversation IDs to turn metadata and error replies. |
app/channels/web_channel.py |
Tracks, broadcasts, and replays busy and turn frames across WebSocket connections. |
app/channels/frontend/app.js |
Handles replayed frames, conversation filtering, deduplication, and switch locking. |
app/channels/frontend/app.css |
Adds visual disabled and locked states for conversation controls. |
Review effort: Lite
|
What would you like me to review about this PR? Please include the specific question, or a finding’s title/location if you’re referring to one. |
Code Review by Qodo
1.
|
- Fork re-checks the switch lock when clicked (the menu may predate the turn) and again after loading the history. - Only a message marked not_started (the restart refusal) clears the pending-turn dots; scheduled-task output no longer unlocks switching. - Catch-up: when the loaded history already ends with a held turn's reply, drop that turn's held lines instead of showing them (and the reply again) below it. - A held frame that starts another reload hands the remaining frames to that reload instead of drawing them above its history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dwjz3bkBQygqJ7jmUd7Tb
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6faa631a97
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The busy frame a tab gets on connect is now marked replay: its turn began before the tab loaded the history, so the user message is already there. A busy: true that merely waited while the history loaded (another tab started a turn meanwhile) no longer counts as covered, so the tab reloads the conversation and shows that turn's user message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dwjz3bkBQygqJ7jmUd7Tb
There was a problem hiding this comment.
🟡 Changes recommended
A critical concurrency issue remains because conversation-switch commands can still be accepted while another turn is active, allowing the shared agent to switch and lose the in-progress reply.
1 open issue
1 resolved since last review
Review effort: Lite


Problem
/load, which waited behind the running turn. The UI cleared the chat right away, so the turn's progress lines were lost and its later replies showed up in the conversation you switched to.Changes
Server
BackgroundAgent.process_incoming()addsconversation_idto a turn's metadata, so thinking text, tool status lines, the reply, errors and the trajectory frame all carry it.busyframes now include the turn'sconversation_id.WebChannel.send_message()sends every turn frame to all tabs. Command replies (system) still go only to the tab that sent the command.WebChannelrecords what has been delivered: the last busy frame and the running turn's frames, capped at 500. A tab that connects gets these first. It holds that tab's send lock during the replay, so frames sent in the meantime wait until the replay is done. On replay,stoppedis reset to false.Frontend (
app.js)/new,/loadand/fork. A sidebar click shows a notice explaining why.busy: truefor a turn this tab didn't start reloads that conversation from the history, so a second tab sees the user message that started it.CLAUDE.md is updated to describe the new behaviour.
Testing
uv run pytest: 486 passed. New tests cover sending turn frames to all tabs, command replies going to one tab, catch-up for a tab opened mid-turn, frames not replayed after the turn ends, the replay cap, scheduled-task output not counted as a turn frame, and the conversation tag on turn metadata.ruff checkandruff formatare clean.pylintreports only a cyclic import inapp/core/tool.py, which this PR doesn't touch.Known limits
Generated by Claude Code