Skip to content

fix(server): mark unread works while a queued message is held - #23593

Open
only21mil wants to merge 2 commits into
pingdotgg:mainfrom
only21mil:r13/a180-mark-unread-uses-the-thread-s-latest-unh
Open

only21mil wants to merge 2 commits into
pingdotgg:mainfrom
only21mil:r13/a180-mark-unread-uses-the-thread-s-latest-unh

Conversation

@only21mil

Copy link
Copy Markdown
Contributor

Problem

Mark unread does nothing on a thread whose queue is held. Here's one way to get there: send a message, queue a follow-up while the agent works, then press Stop. Stop interrupts the run and holds the follow-up. Leave the thread and pick Mark unread from its sidebar menu. No unread mark appears and no error shows.

The server rejects the command with "has no completed run to mark unread". It reads projection.runs.at(-1), the highest-ordinal run, which is now the held queued follow-up, and that run has no completedAt. The thread still has a finished run: the shell already presents the interrupted run as the latest through latestUnheldRun. The web client sends this command with reportFailure: false, so the click fails silently.

The server also holds the queue after a provider failure, when the provider instance is disabled for the project, or when the run's worktree is missing. Mark unread fails the same way there.

Change

Mark unread now takes completedAt from latestUnheldRun(projection.runs) instead of runs.at(-1). That's the helper the thread shell falls back to for its latest run, so after Stop the server picks the same run the sidebar shows. Without a held queue the result is the same as before. A thread with no runs, or whose latest unheld run is still going, is still rejected.

One production line plus the import. One runtime-layer test.

Scope and approval

This is a small, focused fix for an obvious bug under the CONTRIBUTING.md exception. I found no issue or open PR for it. #4873 was about Mark unread resetting on the active thread, a different symptom, and it's closed. No client code changes.

Verification

  • cd apps/server && pnpm exec vp test run src/orchestration-v2/runtimeLayer.test.ts -t "marks unread from the latest run when a newer queued message is held". The new test writes a completed run followed by a held queued run, dispatches thread.mark-unread and checks lastVisitedAt is completedAt minus 1 ms. It also checks the shell's latest run is the completed one. On main it fails with the rejection above; on this branch it passes. The whole file passes, 84 tests.
  • pnpm exec vp fmt --check and pnpm exec vp lint on the two changed files, and pnpm exec tsc --noEmit in apps/server, pass. Lint reports one warning, which was already there (layerUnavailable in Orchestrator.ts).
  • Real app, Linux: production web and server builds of main and this branch, fresh state for each, the repo's ACP mock agent with a 120 s reply, headless Chromium. Same steps on both builds: send, queue a follow-up, Stop, open a new thread, right-click the stopped thread, Mark unread. The server reply was read over the WebSocket and the stored rows were read read-only.

Not checked: desktop and mobile apps, the provider-failure, disabled-instance and missing-worktree hold paths in the real app (the test writes the held run directly), and the full server suite.

Prior PRs

#14616 by Julius Marminge made the shell's latest run skip held queued runs with latestUnheldRun. This PR uses the same helper for Mark unread. No closed PR tried this fix.

Evidence

On main the new test fails with the server's rejection.

New test failing on main

On this branch it passes.

New test passing on this branch

Main build after Stop: the first run is interrupted and the follow-up stays queued with Send now. The branch build reaches the same state.

Thread after Stop with the follow-up held

Main build after Mark unread: the stopped thread shows no unread mark.

Main: no unread mark after Mark unread

Branch build after Mark unread: the stopped thread shows Done.

Branch: Done after Mark unread

Stored rows and the server's reply for both runs. Main rejects the command and lastVisitedAt stays put. The branch accepts it and lastVisitedAt moves 1 ms before the interrupted run's completedAt.

Rows and server replies for both builds

Production fix, tests, PR description and images written with Claude Opus 5.5 in T3 Code Nightly.

🤖 Generated with Claude Opus 5.5 in T3 Code Nightly

Mark unread read the newest run, which can be a queued message held after
Stop or a provider failure. That run has no completedAt, so the command
failed and the click did nothing. Use latestUnheldRun, the same run the
thread shell shows as latest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 5127302 and 8dfc293.

📒 Files selected for processing: 2
  • apps/server/src/orchestration-v2/Orchestrator.ts
  • apps/server/src/orchestration-v2/runtimeLayer.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

📝 Walkthrough

thread.mark-unread now sets the unread watermark from the latest unheld run. A regression test covers the case where a newer queued run is held.

Changes: Unread watermark

Layer / File(s) Summary
Select the latest unheld run
apps/server/src/orchestration-v2/Orchestrator.ts, apps/server/src/orchestration-v2/runtimeLayer.test.ts
The handler uses the latest unheld run’s completion time. The regression test verifies the watermark when a newer queued run is held.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge, t3dotgg, yash-singh1

Merge Risk: ⚪ Minimal · up to 8dfc2

Mark unread can use the completed run when a newer queued message is held. No identified issue prevents merging after normal checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary server fix: Mark unread now works when a queued message is held.
Description check Passed The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the failure, implementation, test results, manual verification, limitations, and prior app…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the failure, implementation, test results, manual verification, limitations, and prior approval rationale.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Oct 22, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at de09bed

Macroscope's review found this PR approvable — This is a narrowly scoped server bug fix that aligns mark-unread with the thread’s existing latest-unheld-run presentation logic. It adds a targeted regression test and does not alter schemas, defaults, infrastructure, or sensitive code paths.

You can add or adjust custom eligibility rules. Learn more.

…st-unh

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant