Repository navigation
Session History: resume folder workspace chats in their own folder - #27209
Conversation
…older Session History matched a session to its workspace only among git worktrees, so a conversation recorded in a folder workspace had no owner: its subagent rows could never resume, and the parent row only offered whatever workspace happened to be active. Folder workspaces now join the owner list as their usual folder: rows.
…hout a second toast Both Session History jumps and the resume activation now go through activateAndRevealWorkspace, which owns the folder routing and its path gate. A folder the gate refuses already shows why, so the generic 'no longer available' toast is kept only for a workspace that is actually gone.
…d not pick a folder Folder activation only explains a refusal once it resolves the folder's owner; an id present on two hosts returns before any toast. Suppress the generic toast only when that owner resolves.
Review summary (head
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to Folder sessions with duplicate IDs across hosts can fail to open, and may target the active host rather than the session's owner. Resolve host-qualified activation before merging. Pre-merge checks |
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
d633f68c-7955-483b-99be-cd0bea3206d1
📒 Files selected for processing (11)
src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultPanel.tsxsrc/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.test.tssrc/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.tssrc/renderer/src/components/right-sidebar/ai-vault-session-worktree-map.test.tsxsrc/renderer/src/components/right-sidebar/ai-vault-session-worktree.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-worktree.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.parity.test.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.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.
| } | ||
|
|
||
| function revealSessionWorkspace(workspaceId: string): boolean { | ||
| if (activateAndRevealWorkspace(workspaceId) !== false) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 12 'function activateAndRevealFolderWorkspace|function activateAndRevealWorkspace|findFolderWorkspaceOwner|getPreferredFolderExecutionHostId' \
src/renderer/src/lib/worktree-activation.ts \
src/renderer/src/lib/folder-workspace-runtime-owner.ts
rg -n -C 5 'activateAiVaultResumeWorkspace\(|jumpToWorktree\(' src/renderer/src/componentsRepository: stablyai/orca
Length of output: 29313
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- original pane actions ---'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.ts | sed -n '1,180p'
printf '%s\n' '--- resume launch ---'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.ts | sed -n '1,180p'
printf '%s\n' '--- activation owner implementation ---'
nl -ba src/renderer/src/lib/worktree-activation.ts | sed -n '80,125p;333,365p'
nl -ba src/renderer/src/lib/folder-workspace-runtime-owner.ts | sed -n '1,80p'
printf '%s\n' '--- indexed owner definitions/usages ---'
rg -n -C 8 --glob '*.ts' -- 'findIndexedFolderWorkspaceOwner|function findIndexedFolderWorkspaceOwner|const findIndexedFolderWorkspaceOwner' src/renderer/src src/shared
printf '%s\n' '--- session host fields and callers ---'
rg -n -C 6 --glob '*.ts' -- 'executionHostId.*targetId|targetId.*executionHostId|activateAiVaultResumeWorkspace\(|jumpToWorktree\(' src/renderer/src/components/right-sidebarRepository: stablyai/orca
Length of output: 42315
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- indexed folder owner implementation ---'
nl -ba src/renderer/src/lib/worktree-runtime-owner-index.ts | sed -n '1,340p'
printf '%s\n' '--- original pane target definition and session types ---'
rg -n -C 12 --glob '*.ts' -- 'findOriginalAiVaultSessionPane|interface AiVaultSession|type AiVaultSession|executionHostId' \
src/renderer/src/components/right-sidebar/ai-vault-original-pane.ts \
src/shared/ai-vault-types.ts \
src/renderer/src/components/right-sidebar/ai-vault-original-pane-index.ts
printf '%s\n' '--- target construction and resume target identity ---'
rg -n -C 12 --glob '*.ts' -- 'targetId|worktreeId:.*session|executionHostId:.*session|jumpToWorktree\(' \
src/renderer/src/components/right-sidebar src/renderer/src/libRepository: stablyai/orca
Length of output: 45667
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- original pane target shape and construction ---'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-original-pane.ts | sed -n '1,70p;180,250p'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-original-pane-index.ts | sed -n '1,95p'
printf '%s\n' '--- original-pane action call sites ---'
rg -n -C 8 --glob '*.tsx' --glob '*.ts' -- 'jumpToOriginalPane|jumpToWorktree|getOriginalPaneTarget' src/renderer/src/components/right-sidebar
printf '%s\n' '--- resume action inputs and target construction ---'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts | sed -n '1,180p'
rg -n -C 10 --glob '*.ts' --glob '*.tsx' -- 'targetId\s*=|targetId\s*:|executionHostId' src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts src/renderer/src/components/right-sidebar/*session*history* src/renderer/src/components/right-sidebar/*vault*Repository: stablyai/orca
Length of output: 45666
🏁 Script executed:
#!/bin/bash
set -euo pipefail
nl -ba src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts | sed -n '60,165p'
rg -n -C 6 --glob '*.tsx' --glob '*.ts' -- 'jumpToWorktree\(' src/renderer/src/components/right-sidebar
rg -n -C 6 --glob '*.tsx' --glob '*.ts' -- 'resumeAiVaultSessionInNewChat|activateAiVaultResumeWorkspace' src/renderer/src/components/right-sidebarRepository: stablyai/orca
Length of output: 32053
Pass executionHostId through folder activation.
When the same folder ID exists on multiple hosts, the unqualified lookup is ambiguous and activation can return false. Pass session.executionHostId to activateAndRevealWorkspace for original-pane jumps, folder-row jumps, terminal resume, and resume-in-chat. Pass the same host to the fallback findFolderWorkspaceOwner check.
Update activateAiVaultResumeWorkspace to accept the host and forward { executionHostId }. Update its callers to pass the session host.
📍 Affects 2 files
src/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.ts#L118-L118(this comment)src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.ts#L14-L14
Folder rows now read 'Resume in Folder', 'Folder' (details heading), 'Current folder' / 'Folder' / 'Archived folder', 'Jump to Folder', and 'Folder workspace is no longer available.'. The kind is derived from the row's folder: workspace id; git worktree rows are unchanged.
…rows Also shows a plain folder icon on a folder row's details heading and aligns the Spanish and Japanese folder strings with their worktree counterparts.
…27209) * fix(session-history): resume folder workspace sessions in their own folder Session History matched a session to its workspace only among git worktrees, so a conversation recorded in a folder workspace had no owner: its subagent rows could never resume, and the parent row only offered whatever workspace happened to be active. Folder workspaces now join the owner list as their usual folder: rows. * test(session-history): mock folderWorkspaces as the array the store holds * fix(session-history): jump through the shared workspace activator without a second toast Both Session History jumps and the resume activation now go through activateAndRevealWorkspace, which owns the folder routing and its path gate. A folder the gate refuses already shows why, so the generic 'no longer available' toast is kept only for a workspace that is actually gone. * fix(session-history): keep the jump toast when folder activation could not pick a folder Folder activation only explains a refusal once it resolves the folder's owner; an id present on two hosts returns before any toast. Suppress the generic toast only when that owner resolves. * test(session-history): type the folder fixture without an assertion * fix(session-history): call folder workspaces folders, not worktrees Folder rows now read 'Resume in Folder', 'Folder' (details heading), 'Current folder' / 'Folder' / 'Archived folder', 'Jump to Folder', and 'Folder workspace is no longer available.'. The kind is derived from the row's folder: workspace id; git worktree rows are unchanged. * fix(session-history): say Jump to Folder in the row menus for folder rows Also shows a plain folder icon on a folder row's details heading and aligns the Spanish and Japanese folder strings with their worktree counterparts. (cherry picked from commit bedf6f5)
ELI5
Agent Session History knows which workspace each saved chat belongs to by looking at the folder the chat ran in. It only ever looked among git worktrees. A chat that ran in a folder workspace (a plain folder, not a git worktree) therefore had no workspace, and resuming it either didn't work or opened it in the wrong place. Now folder workspaces count as owners too, so their chats resume back into their own folder, just like a worktree's chats resume into their worktree.
What Changed
The problem (found in #26798's live QA and reproduced on
main): open a folder workspace that has saved agent chats, then open Session History:After:
A chat (and its subagents) recorded in a folder workspace resumes into that folder from Session History and from the tab menu, whichever workspace is active.
The row shows the folder's name, plus "Current folder" when you're in it.
Jump takes you to the folder.
Folder rows are labelled as folders, never as worktrees: "Resume in Folder", a "Folder" heading, "Current folder" / "Folder" / "Archived folder", "Jump to Folder", and "Folder workspace is no longer available.". Before, they said "Unavailable worktree". Translated in all six languages.
Chats in git worktrees, SSH workspaces and WSL workspaces behave exactly as before, wording included.
Mechanism: the session→workspace match (
buildWorktreeCandidates, fed by the panel'suseAiVaultSessionWorktreeMapand the tab menu'sresolveTabSessionSwitch) now receives the git worktrees plus folder workspaces as their standardfolder:<id>rows (folderWorkspaceToWorktree, the same adapter the sidebar, dashboard and Linear views use).Everything downstream already accepted a
folder:target: resume gating, launch, host checks and folder activation. So the fix is one shared list,aiVaultSessionOwnerWorkspaces, used by both surfaces.activateAndRevealWorkspace, and so does the panel's resume activation. That path routesfolder:ids to folder activation, which checks the folder still exists and is reachable, and never sets a fake active project. The generic "Worktree is no longer available." toast stays for a workspace that's gone. It's skipped only when folder activation already showed its own reason, so the user never gets two toasts.Why
The bug came from the ownership list missing a whole kind of workspace, not from the resume gate. A guard in the resume path (for example, "if the active workspace is a folder, allow it") would have fixed only the active-folder case. Subagent rows and the "Unavailable worktree" label would still be wrong. Adding folder workspaces to the one list every Session History surface reads fixes all of these and reuses the existing workspace-row concept.
The handoff suspected the panel's
activeWorktreeIdgate. I checked: a folder workspace's active id is alreadyfolder:<id>, and that gate accepts it. The parent row in an active folder was never the broken case.Linked Issue
Found in #26798 live QA. No separate issue.
Visual Proof
Hidden test app with the same setup on both sides: a git project
repo, a folder workspace "Notes" (a plain folder in a project group), and saved conversations recorded in that folder. Stand-in agent programs record where Orca launched them. "After" shots are from the final headcaa3ec3c887.1. Subagent of a folder conversation (OMP, the agent whose subagents can be resumed on their own)
main)2. Folder conversation while a git worktree is active (Claude)
main)mainstays selected, though the shell is innotes).Testing
New tests:
Ablation: with the fix reverted, 7 of the new tests fail.
All Session History and tab-menu test files pass (46 files, 428+ tests).
Label tests: folder rows read Folder in every label and menu; git rows keep the worktree text.
Jump feedback tests: one toast for a missing workspace or a folder id that exists on two hosts, none extra when folder activation already explained why.
tc:webandorca-ci-checkspass.No main-process change: the main side (resume preparation, launch,
agentSession.reveal) already handlesfolder:workspace ids.Live QA (macOS, hidden test app with stand-in agents, no real agent logins): before (
maindab3f6e) and after (branch). The driver was Codex gpt-5.6-terra.main; enabled on the branch, resumes into Notes from either workspace.mainand the branch.~/.claude,~/.codexand~/.orcawere hashed before and after: unchanged.mainand the branch. Also not verified live: SSH, WSL and Windows; those are covered by unit tests only.I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Agent skill upstream boundary
Notes
folder:<id>workspace on the host side (session-search-scope-resolution.ts). That's a listing/search gap, separate from resume.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)