feat(web): hide subagent threads from user-facing thread lists (5/5) - #4664
feat(web): hide subagent threads from user-facing thread lists (5/5)#4664shivamhwp wants to merge 6 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
940e8fb to
c3a50f8
Compare
d2ef6c7 to
adb1e9b
Compare
c3a50f8 to
8c6a796
Compare
adb1e9b to
39912b0
Compare
8c6a796 to
103b937
Compare
39912b0 to
6bc3a8d
Compare
103b937 to
31adb36
Compare
6bc3a8d to
05fe44c
Compare
31adb36 to
b270c47
Compare
05fe44c to
7458298
Compare
b270c47 to
daf1a88
Compare
7458298 to
4daeac4
Compare
daf1a88 to
1dce464
Compare
4daeac4 to
095cab7
Compare
1dce464 to
4c169ed
Compare
095cab7 to
1d37a6f
Compare
4c169ed to
70d5111
Compare
1d37a6f to
6cf67a2
Compare
70d5111 to
c78d0ff
Compare
6cf67a2 to
5e3c535
Compare
A subagent's child thread is an implementation detail of the agent that owns it, not a conversation the user started. Listing them alongside real threads makes the sidebar grow with every delegation. Filtered at the source — the shell snapshot the server serves — so every client sees the same set rather than each re-deriving it. The web client filters again for locally-held state, and the thread route redirects to the parent if a subagent thread is reached directly. Mobile is part of the sweep, not left behind. It still offered "Open subagent thread" and had no redirect to fall back on, so it would have navigated to a thread the client no longer receives. Its relationships banner also needed the same subagent-edge filter web has: those edges are derived from the projection rather than from thread shells, so they survive the shell filtering and would have rendered as "Unavailable" — and, sorting first, could have taken over the banner headline. Trade-off worth stating plainly: a subagent's transcript becomes unreachable. The Agents panel added in the previous PR shows what an agent did and what it returned, but not its full conversation. That is the intended shape here, and the "Open subagent thread" affordances are removed on both clients rather than left as dead ends.
Removing the "Open subagent thread" affordance left the mobile work log subscribing to item support it no longer reads, and the web sidebar importing a predicate it no longer calls. Both tripped the lint rule the CI Check job enforces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
c78d0ff to
a997806
Compare
5e3c535 to
2430736
Compare
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. This feature PR has critical unresolved review comments identifying that worktree orphan detection may incorrectly delete worktrees still in use by hidden subagent threads, plus an inconsistent redirect predicate in mobile. These potential bugs require human review before merging. You can customize Macroscope's approvability policy. Learn more. |
Worktree bookkeeping now reads every thread in the environment, so deleting a parent no longer flags a worktree as orphaned while hidden subagent threads still hold it. Mobile thread routes redirect internal subagent threads to their parent instead of rendering the hidden transcript, matching the web route guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // Internal subagent threads are hidden from user-facing lists; a deep | ||
| // link or stale selection still lands here, so send it to the parent | ||
| // thread instead of exposing the hidden transcript. | ||
| if (selectedThread.lineage.relationshipToParent === "subagent") { |
There was a problem hiding this comment.
🟡 Medium threads/ThreadRouteScreen.tsx:185
The subagent redirect only checks selectedThread.lineage.relationshipToParent === "subagent", but internal subagent threads also include node-fork threads (forkedFrom.type === "node"). When a deep link or stale selection resolves to a node-fork thread, this branch is skipped and ThreadRouteContent renders the hidden transcript instead of redirecting to the parent. The guard should match the same predicate used elsewhere (e.g., isInternalSubagentThread) so node-fork threads are redirected as well.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/threads/ThreadRouteScreen.tsx around line 185:
The subagent redirect only checks `selectedThread.lineage.relationshipToParent === "subagent"`, but internal subagent threads also include node-fork threads (`forkedFrom.type === "node"`). When a deep link or stale selection resolves to a node-fork thread, this branch is skipped and `ThreadRouteContent` renders the hidden transcript instead of redirecting to the parent. The guard should match the same predicate used elsewhere (e.g., `isInternalSubagentThread`) so node-fork threads are redirected as well.
| /** Every thread in the environment, including internal subagent threads. | ||
| Worktree bookkeeping must see all threads that can hold a worktree | ||
| reference, or deleting a parent reads its subagents' worktree as orphaned. */ | ||
| export function readEnvironmentAllThreadRefs( |
There was a problem hiding this comment.
🔴 Critical state/entities.ts:229
readEnvironmentAllThreadRefs reads environmentThreadShells.threadShellsAtom, but that snapshot no longer contains internal subagent shells after the server-side filtering change. The function therefore returns only user-facing threads, not "every thread in the environment" as its doc comment claims. In deleteThread, the survivingThreads list passed to getOrphanedWorktreePathForThread is built from this filtered set, so a worktree still referenced by a hidden subagent is classified as orphaned and can be force-removed — destroying a worktree that remains in use, including any uncommitted contents. The source of truth for all shells (including internal subagents) needs to be used here, or the worktree-orphan check needs to consult a source that still includes subagent threads.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/state/entities.ts around line 229:
`readEnvironmentAllThreadRefs` reads `environmentThreadShells.threadShellsAtom`, but that snapshot no longer contains internal subagent shells after the server-side filtering change. The function therefore returns only user-facing threads, not "every thread in the environment" as its doc comment claims. In `deleteThread`, the `survivingThreads` list passed to `getOrphanedWorktreePathForThread` is built from this filtered set, so a worktree still referenced by a hidden subagent is classified as orphaned and can be force-removed — destroying a worktree that remains in use, including any uncommitted contents. The source of truth for all shells (including internal subagents) needs to be used here, or the worktree-orphan check needs to consult a source that still includes subagent threads.
…into stack-05 # Conflicts: # apps/server/src/orchestration-v2/ThreadManagementService.test.ts # apps/server/src/ws.ts
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d5f9b62. Configure here.
| ? allThreads.filter( | ||
| (entry) => entry.id === threadRef.threadId || !deletedIds.has(entry.id), | ||
| ) | ||
| : allThreads; |
There was a problem hiding this comment.
Orphan worktree misses hidden subagents
High Severity
The filtering of non-user-facing threads from the client-side threadShellsAtom means readEnvironmentAllThreadRefs no longer includes hidden subagents. This leads to inaccurate worktree orphan detection during thread deletion, where the UI might offer to delete worktrees still in use by active subagents.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d5f9b62. Configure here.


Last of five stacked PRs replacing #4551. Stacked on #4663 — review that first.
A subagent's child thread is an implementation detail of the agent that owns it, not a conversation the user started. Listing them alongside real threads makes the sidebar grow with every delegation.
Filtered at the source — the shell snapshot the server serves — so every client sees the same set rather than each re-deriving it. The web client filters again for locally-held state, and the thread route redirects to the parent if a subagent thread is reached directly.
Mobile is part of the sweep
Found in review, and the reason this isn't a web-only change. Mobile still offered "Open subagent thread" with no redirect to fall back on, so it would have navigated to a thread the client no longer receives. Its relationships banner also needed the same subagent-edge filter web has — those edges derive from the projection rather than from thread shells, so they survive the shell filtering and would have rendered as "Unavailable", and by sorting first could have taken over the banner headline.
The trade-off, stated plainly
A subagent's transcript becomes unreachable. The Agents panel from #4663 shows what an agent did and what it returned, but not its full conversation. That is the intended shape here, and the "Open subagent thread" affordances are removed on both clients rather than left as dead ends — but it is a real capability loss and worth a deliberate decision rather than being smuggled in with a sidebar tidy-up.
Testing
Typecheck clean on server, web, client-runtime. Server 1,685 passing, web 1,500 passing, client-runtime 485 passing. (
apps/mobilehas 70 pre-existing typecheck errors on the base branch, unchanged by this PR.)Known, not fixed here
/rather than the parent. It matters under version skew, so it is kept.entities.tsswaps memoized ref atoms for per-call filter+map. The cost is negligible but the identity stability is not; filtering inside the client-runtime atoms instead would be the better shape.thread.removeddelta, absorbed harmlessly by the reducer.Note
Hide subagent threads from all user-facing thread lists and routes
isInternalSubagentThread(server) and a matching client-side predicate in threadVisibility.ts that classify threads as internal subagents when their lineage issubagentor they are forked from a node.useThreadShells,useThreadRefs, and related hooks now return a filtered set; any caller that needs the full set must use the newreadEnvironmentAllThreadRefs.Macroscope summarized d5f9b62.
Note
Medium Risk
Changes thread visibility and routing across server snapshots, WebSocket streams, and both clients; worktree deletion logic now depends on the full thread set via readEnvironmentAllThreadRefs. Low security impact but moderate product/behavior risk if predicates or filters miss edge cases.
Overview
Internal subagent threads (lineage
subagentor forked from a node) are treated as implementation detail and removed from what users see in lists, snapshots, and navigation—while the server can still hold them for worktree/orchestration bookkeeping.The server applies
userFacingShellSnapshoton HTTP and WebSocket shell loads and drops internal subagent deltas from live shell streams.isInternalSubagentThread/isUserFacingThreadcentralize the rule on web (threadVisibility.ts) and align list APIs and auto-bootstrap thread selection.Web filters thread refs and shells in
entities.ts, addsreadEnvironmentAllThreadRefsfor worktree orphan checks that must include hidden subagents, redirects the chat route to the parent thread, strips subagent edges from relationship panels, and removes “open subagent thread” from timeline, inspector, and work-log links. Mobile mirrors redirect, relationship filtering, and work-log link removal.Trade-off: full subagent transcripts are no longer reachable via thread navigation; activity UI still shows subagent work without opening the child thread.
Reviewed by Cursor Bugbot for commit d5f9b62. Bugbot is set up for automated code reviews on this repo. Configure here.