Bug
Creating a new project successfully spawns an orchestrator session, but the UI navigates to /projects/$projectId/sessions/$sessionId before useWorkspaceQuery's cache contains the new session. The route renders an empty/"Session not found" view until a later refetch catches up (15s interval or manual click).
Source: Discord (#bug-triaging) | Reported by: Cruzer | Analyzed against: 2155c3c
Confidence: High — traced the exact code path and React Query v5.101.0 source
Reproduction
- Add a new project via the "New project" button in the sidebar.
- The
createProject callback in _shell.tsx runs: creates project → optimistic update → spawns orchestrator → invalidateQueries → navigate to the session route.
SessionView mounts, looks up the session from useWorkspaceQuery().data — it's not there.
- UI shows "Session not found. It may have been cleaned up — pick another from the sidebar." (or a blank terminal area depending on timing).
- The session appears only after the 15s
refetchInterval fires, or after the user clicks the orchestrator again (which re-spawns + invalidates).
Root Cause
The createProject callback in frontend/src/renderer/routes/_shell.tsx (lines 58–109) has a timing flaw:
// Line 94 — optimistic update: workspace added with sessions: []
updateWorkspaces((current) => [workspace, ...current.filter((item) => item.id !== workspace.id)]);
// Lines 96–101
const sessionId = await spawnOrchestrator(workspace.id);
await queryClient.invalidateQueries({ queryKey: workspaceQueryKey });
void navigate({ to: "/projects/$projectId/sessions/$sessionId", params: { ... } });
Three factors combine:
-
setQueryData resets dataUpdatedAt — The optimistic updateWorkspaces() (line 94) calls queryClient.setQueryData(), which sets dataUpdatedAt = Date.now() and marks the query as fresh.
-
invalidateQueries swallows refetch errors — In React Query v5.101.0, refetchQueries does promise.catch(noop) (queryClient.js:174). So await invalidateQueries(...) always resolves, even if the refetch fails. If the daemon is briefly busy during the heavy spawn workload (git worktree creation, workspace provisioning, Zellij runtime launch), the GET /api/v1/sessions refetch can fail — and invalidateQueries resolves anyway, leaving the cache with the optimistic sessions: [] data.
-
staleTime: 10_000 suppresses mount-triggered refetch — The global QueryClient config (frontend/src/renderer/lib/query-client.ts:6) sets staleTime: 10_000. When SessionView mounts and its useWorkspaceQuery() observer registers, it does NOT trigger a refetch because Date.now() - dataUpdatedAt < 10_000 (the setQueryData at step 1 just reset the timestamp). The session stays missing until the 15s refetchInterval in workspaceQueryOptions fires.
The backend is not the problem — Manager.Spawn (backend/internal/session_manager/manager.go:191) persists the session to the SQLite store synchronously before returning, so GET /api/v1/sessions would return it on a successful refetch. The issue is the React Query cache not being reliably refreshed before navigation.
Note: The existing-orchestrator respawn path in Sidebar.tsx (lines 412–415) has the same invalidateQueries → navigate pattern but is less likely to hit this because it doesn't do the setQueryData optimistic update that resets dataUpdatedAt.
Fix
The most robust approach: optimistically inject the spawned session into the workspace's sessions array after spawnOrchestrator returns the sessionId, so SessionView can find it immediately regardless of refetch success:
const sessionId = await spawnOrchestrator(workspace.id);
updateWorkspaces((current) =>
current.map((w) =>
w.id === workspace.id
? { ...w, sessions: [
{ id: sessionId, workspaceId: w.id, workspaceName: w.name, title: "orchestrator",
kind: "orchestrator", branch: `session/${sessionId}`,
status: "working", createdAt: new Date().toISOString(), prs: [] },
...w.sessions,
] }
: w,
),
);
await queryClient.refetchQueries({ queryKey: workspaceQueryKey }); // forces fresh data ASAP
void navigate({ ... });
Secondary improvement: soften the SessionView "Session not found" guard (SessionView.tsx:140) to show a brief loading/retry state for recently navigated sessions instead of immediately declaring the session missing.
Impact
- Severity: Medium — every new project creation hits a broken-looking empty view. Workaround exists (wait 15s or re-click orchestrator) but the UX is confusing.
- Affected: All users on the latest main (
2155c3c), Electron app.
Related
frontend/src/renderer/routes/_shell.tsx:58–109 — createProject callback
frontend/src/renderer/hooks/useWorkspaceQuery.ts:67–76 — workspaceQueryOptions (15s refetch interval)
frontend/src/renderer/lib/query-client.ts:3–9 — global staleTime: 10_000
frontend/src/renderer/components/SessionView.tsx:140–146 — "Session not found" guard
- React Query v5.101.0
queryClient.js:174 — refetchQueries swallows fetch errors
Bug
Creating a new project successfully spawns an orchestrator session, but the UI navigates to
/projects/$projectId/sessions/$sessionIdbeforeuseWorkspaceQuery's cache contains the new session. The route renders an empty/"Session not found" view until a later refetch catches up (15s interval or manual click).Source: Discord (#bug-triaging) | Reported by: Cruzer | Analyzed against:
2155c3cConfidence: High — traced the exact code path and React Query v5.101.0 source
Reproduction
createProjectcallback in_shell.tsxruns: creates project → optimistic update → spawns orchestrator →invalidateQueries→navigateto the session route.SessionViewmounts, looks up the session fromuseWorkspaceQuery().data— it's not there.refetchIntervalfires, or after the user clicks the orchestrator again (which re-spawns + invalidates).Root Cause
The
createProjectcallback infrontend/src/renderer/routes/_shell.tsx(lines 58–109) has a timing flaw:Three factors combine:
setQueryDataresetsdataUpdatedAt— The optimisticupdateWorkspaces()(line 94) callsqueryClient.setQueryData(), which setsdataUpdatedAt = Date.now()and marks the query as fresh.invalidateQueriesswallows refetch errors — In React Query v5.101.0,refetchQueriesdoespromise.catch(noop)(queryClient.js:174). Soawait invalidateQueries(...)always resolves, even if the refetch fails. If the daemon is briefly busy during the heavy spawn workload (git worktree creation, workspace provisioning, Zellij runtime launch), theGET /api/v1/sessionsrefetch can fail — andinvalidateQueriesresolves anyway, leaving the cache with the optimisticsessions: []data.staleTime: 10_000suppresses mount-triggered refetch — The global QueryClient config (frontend/src/renderer/lib/query-client.ts:6) setsstaleTime: 10_000. WhenSessionViewmounts and itsuseWorkspaceQuery()observer registers, it does NOT trigger a refetch becauseDate.now() - dataUpdatedAt < 10_000(thesetQueryDataat step 1 just reset the timestamp). The session stays missing until the 15srefetchIntervalinworkspaceQueryOptionsfires.The backend is not the problem —
Manager.Spawn(backend/internal/session_manager/manager.go:191) persists the session to the SQLite store synchronously before returning, soGET /api/v1/sessionswould return it on a successful refetch. The issue is the React Query cache not being reliably refreshed before navigation.Note: The existing-orchestrator respawn path in
Sidebar.tsx(lines 412–415) has the sameinvalidateQueries→navigatepattern but is less likely to hit this because it doesn't do thesetQueryDataoptimistic update that resetsdataUpdatedAt.Fix
The most robust approach: optimistically inject the spawned session into the workspace's
sessionsarray afterspawnOrchestratorreturns the sessionId, soSessionViewcan find it immediately regardless of refetch success:Secondary improvement: soften the
SessionView"Session not found" guard (SessionView.tsx:140) to show a brief loading/retry state for recently navigated sessions instead of immediately declaring the session missing.Impact
2155c3c), Electron app.Related
frontend/src/renderer/routes/_shell.tsx:58–109—createProjectcallbackfrontend/src/renderer/hooks/useWorkspaceQuery.ts:67–76—workspaceQueryOptions(15s refetch interval)frontend/src/renderer/lib/query-client.ts:3–9— globalstaleTime: 10_000frontend/src/renderer/components/SessionView.tsx:140–146— "Session not found" guardqueryClient.js:174—refetchQueriesswallows fetch errors