Handle missing workspaces across server and UI#1523
Handle missing workspaces across server and UI#1523juliusmarminge wants to merge 1 commit intomainfrom
Conversation
- validate workspace paths before git, provider, terminal, and WS ops - surface workspace availability in projections and client state - add tests for missing cwd recovery and status handling
|
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 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)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Draft thread effectiveCwd is null, breaking git/terminal features
- Added projectCwd parameter to buildLocalDraftThread and passed fallbackDraftProject.cwd from the call site, so effectiveCwd now falls back to the project's cwd when no worktreePath is set, matching the server-side projection logic.
- ✅ Fixed: Redundant workspace assertion in statusDetails before execute
- Removed the redundant ensureGitWorkspace call in statusDetails and the single-use ensureGitWorkspace helper, since the execute wrapper already calls assertWorkspaceDirectory for every git command.
Or push these changes by commenting:
@cursor push b9268a3495
Preview (b9268a3495)
diff --git a/apps/server/src/git/Layers/GitCore.ts b/apps/server/src/git/Layers/GitCore.ts
--- a/apps/server/src/git/Layers/GitCore.ts
+++ b/apps/server/src/git/Layers/GitCore.ts
@@ -673,20 +673,6 @@
}),
);
- const ensureGitWorkspace = (operation: string, cwd: string, args: readonly string[] = []) =>
- assertWorkspaceDirectory(cwd, operation).pipe(
- Effect.mapError(
- (error) =>
- new GitCommandError({
- operation,
- command: quoteGitCommand(args),
- cwd,
- detail: error.message,
- cause: error,
- }),
- ),
- );
-
const runGit = (
operation: string,
cwd: string,
@@ -1073,11 +1059,6 @@
});
const statusDetails: GitCoreShape["statusDetails"] = Effect.fn("statusDetails")(function* (cwd) {
- yield* ensureGitWorkspace("GitCore.statusDetails", cwd, [
- "status",
- "--porcelain=2",
- "--branch",
- ]);
yield* refreshStatusUpstreamIfStale(cwd).pipe(Effect.ignoreCause({ log: true }));
const [statusStdout, unstagedNumstatStdout, stagedNumstatStdout] = yield* Effect.all(
diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts
--- a/apps/web/src/components/ChatView.logic.ts
+++ b/apps/web/src/components/ChatView.logic.ts
@@ -19,6 +19,7 @@
draftThread: DraftThreadState,
fallbackModelSelection: ModelSelection,
error: string | null,
+ projectCwd: string | null,
): Thread {
return {
id: threadId,
@@ -37,8 +38,8 @@
lastVisitedAt: draftThread.createdAt,
branch: draftThread.branch,
worktreePath: draftThread.worktreePath,
- effectiveCwd: draftThread.worktreePath ?? null,
- effectiveCwdSource: draftThread.worktreePath ? "worktree" : null,
+ effectiveCwd: draftThread.worktreePath ?? projectCwd,
+ effectiveCwdSource: draftThread.worktreePath ? "worktree" : projectCwd ? "project" : null,
effectiveCwdState: "available",
turnDiffSummaries: [],
activities: [],
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -486,9 +486,16 @@
model: DEFAULT_MODEL_BY_PROVIDER.codex,
},
localDraftError,
+ fallbackDraftProject?.cwd ?? null,
)
: undefined,
- [draftThread, fallbackDraftProject?.defaultModelSelection, localDraftError, threadId],
+ [
+ draftThread,
+ fallbackDraftProject?.defaultModelSelection,
+ fallbackDraftProject?.cwd,
+ localDraftError,
+ threadId,
+ ],
);
const activeThread = serverThread ?? localDraftThread;
const runtimeMode =| branch: draftThread.branch, | ||
| worktreePath: draftThread.worktreePath, | ||
| effectiveCwd: draftThread.worktreePath ?? null, | ||
| effectiveCwdSource: draftThread.worktreePath ? "worktree" : null, |
There was a problem hiding this comment.
Draft thread effectiveCwd is null, breaking git/terminal features
High Severity
buildLocalDraftThread sets effectiveCwd to draftThread.worktreePath ?? null, which is null for the common case of a draft thread without a worktree. The old code used projectScriptCwd, which fell back to activeProject.cwd. Now in ChatView.tsx, gitCwd is derived as threadWorkspaceAvailable ? (activeThread?.effectiveCwd ?? null) : null, so for draft threads it resolves to null. This disables git branches, diff panel, file search, and the terminal drawer for all new draft threads.
Additional Locations (1)
| "status", | ||
| "--porcelain=2", | ||
| "--branch", | ||
| ]); |
There was a problem hiding this comment.
Redundant workspace assertion in statusDetails before execute
Low Severity
ensureGitWorkspace in statusDetails is redundant because the execute wrapper (line 618–632) already calls assertWorkspaceDirectory for every git command. The three subsequent runGitStdout calls each go through execute, so the workspace is validated four times total. The ensureGitWorkspace helper itself is only used in this one location.
There was a problem hiding this comment.
🟠 High
t3code/apps/server/src/orchestration/Layers/CheckpointReactor.ts
Lines 176 to 193 in ae625d2
When preferSessionRuntime is true and the session provides a valid cwd, the function still checks input.thread.effectiveCwdState !== "available" and returns undefined if the thread's cached state is stale. This incorrectly blocks checkpoint operations even though a usable workspace exists via the session. Consider validating the state of the actually-resolved cwd (as done in handleRevertRequested) rather than the thread's cached property.
const cwd = input.preferSessionRuntime
? (Option.match(fromSession, {
onNone: () => undefined,
onSome: (runtime) => runtime.cwd,
}) ?? fromThread)
: (fromThread ??
Option.match(fromSession, {
onNone: () => undefined,
onSome: (runtime) => runtime.cwd,
}));
- if (input.thread.effectiveCwdState !== "available" || !cwd) {
+ const resolvedState = yield* Effect.promise(() =>
+ inspectWorkspacePathState(cwd ?? input.thread.effectiveCwd ?? ""),
+ );
+ if (resolvedState !== "available" || !cwd) {
return undefined;
}
if (!isGitWorkspace(cwd)) {Also found in 1 other location(s)
apps/web/src/components/BranchToolbar.tsx:50
When there is no
serverThread(draft thread) butdraftThread?.worktreePathis set (e.g., when the user selected a branch that already lives in an existing worktree), the newbranchCwdcomputation ignores the worktree path entirely and falls back toactiveProject.cwd. The old code usedactiveWorktreePath ?? activeProject?.cwd ?? null, which correctly prioritized the worktree path. This causes git operations (branch listing, checkout) to run against the wrong directory.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration/Layers/CheckpointReactor.ts around lines 176-193:
When `preferSessionRuntime` is true and the session provides a valid `cwd`, the function still checks `input.thread.effectiveCwdState !== "available"` and returns `undefined` if the thread's cached state is stale. This incorrectly blocks checkpoint operations even though a usable workspace exists via the session. Consider validating the state of the actually-resolved `cwd` (as done in `handleRevertRequested`) rather than the thread's cached property.
Evidence trail:
apps/server/src/orchestration/Layers/CheckpointReactor.ts lines 159-193 (REVIEWED_COMMIT): `resolveCheckpointCwd` function resolves cwd preferring session runtime (lines 176-185) but checks `input.thread.effectiveCwdState !== "available"` at line 186 regardless of cwd source.
apps/server/src/orchestration/Layers/CheckpointReactor.ts lines 568-607 (REVIEWED_COMMIT): `handleRevertRequested` validates session runtime's cwd state using `inspectWorkspacePathState(sessionRuntime.value.cwd)` at line 596-598, checking the actual resolved cwd's state.
Also found in 1 other location(s):
- apps/web/src/components/BranchToolbar.tsx:50 -- When there is no `serverThread` (draft thread) but `draftThread?.worktreePath` is set (e.g., when the user selected a branch that already lives in an existing worktree), the new `branchCwd` computation ignores the worktree path entirely and falls back to `activeProject.cwd`. The old code used `activeWorktreePath ?? activeProject?.cwd ?? null`, which correctly prioritized the worktree path. This causes git operations (branch listing, checkout) to run against the wrong directory.



Summary
workspaceStateandeffectiveCwdmetadata so the UI can distinguish project vs. worktree availability.Testing
bun fmtbun lintbun typecheckbun run testNote
Medium Risk
Moderate risk because it adds new workspace validation gates across WebSocket routes, git/provider/session flows, and UI queries; incorrect state detection or caching could block previously-working operations.
Overview
Adds a shared server utility (
workspacePaths.ts) to classify and cache workspace path availability (missing/not-a-directory/inaccessible) and to fail fast viaassertWorkspaceDirectory/WorkspacePathError.Propagates workspace availability into orchestration snapshots and contracts by adding
workspaceStateon projects andeffectiveCwd/effectiveCwdSource/effectiveCwdStateon threads, and updates projection snapshot hydration to compute these values from filesystem inspection.Guards workspace-dependent operations to return clearer errors when folders disappear: wraps git execution and
statusDetails, validates provider session start/recovery and provider-command session creation, blocks checkpoint revert/diff when workspace is unavailable, and validates wsServer routes (git/terminal/file search+write/open-in-editor) while formatting operational errors more cleanly.Updates the web app to disable/hide git, terminal, diff, scripts, and path search when the relevant project/worktree is unavailable, shows “Workspace Missing” messaging with actions (relink project, fall back to project root, delete thread), and adjusts React Query options/invalidation to avoid refetch-on-focus and refresh snapshot on focus/visibility changes. Tests are updated/added for the new snapshot fields and missing-workspace failure paths.
Written by Cursor Bugbot for commit ae625d2. This will update automatically on new commits. Configure here.
Note
Handle missing workspaces across server and UI by adding availability checks and user-facing warnings
WorkspaceAvailabilityStateandThreadEffectiveCwdSourceto the contracts schema, and propagatesworkspaceState,effectiveCwd,effectiveCwdSource, andeffectiveCwdStatethrough the server read model, client store, and web types.inspectWorkspacePathStateandassertWorkspaceDirectoryin workspacePaths.ts; git, terminal, provider, and WebSocket route handlers now fail early with structuredWorkspacePathErrororGitCommandErrormessages when a workspace path is missing or inaccessible.enabledflag and no longer auto-refetch on window focus or reconnect (snapshot sync now handles invalidation on focus instead).📊 Macroscope summarized ae625d2. 32 files reviewed, 4 issues evaluated, 1 issue filtered, 1 comment posted
🗂️ Filtered Issues
apps/web/src/components/BranchToolbar.tsx — 0 comments posted, 1 evaluated, 1 filtered
serverThread(draft thread) butdraftThread?.worktreePathis set (e.g., when the user selected a branch that already lives in an existing worktree), the newbranchCwdcomputation ignores the worktree path entirely and falls back toactiveProject.cwd. The old code usedactiveWorktreePath ?? activeProject?.cwd ?? null, which correctly prioritized the worktree path. This causes git operations (branch listing, checkout) to run against the wrong directory. [ Cross-file consolidated ]