Repository navigation
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
ApprovabilityVerdict: Approved at 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>
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 nocompletedAt. The thread still has a finished run: the shell already presents the interrupted run as the latest throughlatestUnheldRun. The web client sends this command withreportFailure: 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
completedAtfromlatestUnheldRun(projection.runs)instead ofruns.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, dispatchesthread.mark-unreadand checkslastVisitedAtiscompletedAtminus 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 --checkandpnpm exec vp linton the two changed files, andpnpm exec tsc --noEmitinapps/server, pass. Lint reports one warning, which was already there (layerUnavailablein Orchestrator.ts).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.
On this branch it passes.
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.
Main build after Mark unread: the stopped thread shows no unread mark.
Branch build after Mark unread: the stopped thread shows Done.
Stored rows and the server's reply for both runs. Main rejects the command and
lastVisitedAtstays put. The branch accepts it andlastVisitedAtmoves 1 ms before the interrupted run'scompletedAt.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