Repository navigation
Opening an archived thread by its link sends you back to the home screen #16443
Description
Observed
The command palette lists a pull request's archived threads as "Archived thread", and the thread relationships control lists archived related threads. Both navigate to /$environmentId/$threadId. For an archived thread, that route never shows the conversation: it redirects to / (or shows an empty pane when the environment has no other threads). Pasting an archived thread's URL behaves the same way.
Reproduction
- Create a thread, send a message, then archive it.
- Open it from the command palette's linked-thread list for its pull request, or from a related thread's relationships control, or paste its
/$environmentId/$threadIdURL. - The app returns to the home screen instead of showing the thread.
Cause
- The shell snapshot the web client keeps holds active threads only; archived ones live in a separate archive snapshot. So
useThreadShellreturnsnullfor an archived thread. ThreadRouteViewtreats a missing shell after bootstrap asmissing(resolveThreadRouteRenderState), does not mountChatView, and its cleanup effect navigates to/.- Even if
ChatViewwere mounted, it uses that same shell as its active thread and renders the empty state without it.
The server can serve the thread: /api/orchestration/threads/:threadId (getThreadSnapshot) and the thread subscription do not filter archived threads, and a truly absent id comes back as not found, which the client turns into status deleted.
Expected
An archived thread opened by its link shows its conversation. A thread that does not exist or was deleted still leaves the route as today.
Environment
macOS, T3 Code main at 9bd1d80. Found from code reading and a unit-level reproduction (shell atoms with an archived thread, then the route's render state) that resolves missing.
Proposed direction
While the active shell lacks the target, keep the route loading and mount the chat so the thread detail loads; treat the route as missing only once the detail reports deleted. The chat then needs a shell for the archived thread, either from the archive shell snapshot (orchestrationEnvironment.archivedShellSnapshot) or by deriving it from the loaded detail (the server's threadShellFromProjection would have to move to a shared package). Which source to use is a maintainer choice.
I haven't written a fix, because the shell source is a design choice.
Found while porting fixes from a downstream fork (Scient). Investigated with Claude Opus 5.5 in Claude Code.
Activity
juliusmarminge commentedon Oct 6, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks @yaacovcorcos for the thorough cause analysis, it lined up closely with what I found in the code.
What I found
main. A pull request's linked-thread list includes archived threads (listLinkedPullRequestThreadsdoesn't filterarchived_at); the command palette labels them "Archived thread" and navigates to/$environmentId/$threadId(buildLinkedThreadActionItems). The lineage panel merges the archive snapshot into the relationship graph and opens the same route, and pasting the URL hits the same view.environmentThreadsAtomreadssnapshot.threads, and archiving publishesthread.removedon the active shell stream, souseThreadShellis null. After bootstrap,resolveThreadRouteRenderStateturns that intomissing,ThreadRouteViewnever mountsChatView, and the URL is replaced with/(or the inset stays empty if there's no other thread).getThreadSnapshotlooks the row up by id without filteringarchivedAt, and a real miss returns 404, which the client stores asdeleted.ChatViewalone wouldn't be enough, since its active thread is the same shell and!activeThreadrendersNoActiveThreadState.useResolvedThreadSelectionloads the thread detail and builds a local shell withthreadDetailToShell, andThreadRouteScreenstays loading until that shell exists.Likely fix area
Some options for the shell source you flagged:
deleted. Keeping that shell local (rather than inserting it into the live shell list) would avoid the archived thread showing up in the sidebar and palette.orchestrationEnvironment.archivedShellSnapshot). Note it's a separate list and can lag the live shell.Either way,
message.dispatchalready rejects archived threads as not active, so the composer would need to stay disabled (or offer unarchive) whenarchivedAtis set. Desktop is affected too, since it wraps the web client.A maintainer will decide on the fix direction.