fix(host-service): classify a missing project directory as NOT_FOUND in workspaces.create
Problem
Creating a workspace for a project whose repository folder no longer exists at its registered path (moved or deleted outside the app) fails with simple-git's construct error ("Cannot use simple-git on a directory that does not exist") thrown from the create mutation's ctx.git(localProject.repoPath) call. It escapes as INTERNAL_SERVER_ERROR, so the user sees an opaque internal error instead of an actionable "project directory is gone" message, and every retry is a fresh 500 — the latest events show one user retrying repeatedly within minutes against a condition that is deterministic and permanent until they re-point or restore the folder. 13 events in the last 24h, 233 lifetime, regressed on 1.23.0.
User-visible harm: a user whose project folder was moved/deleted outside Superset gets a raw internal error on every workspace-create attempt, with no hint that the project path is the problem.
Reach: the create path is unchanged on main; this ships in the next host-service/desktop release and reaches every user creating workspaces on it. The condition is a routine lifecycle state, so it will keep firing until classified.
Why code (vs. archiving / filtering): this issue already regressed once — #6495 classified the same construct error on the git.getStatus path, and the issue came back through the uncovered create path. Archiving would just repeat that cycle, and Sentry-side suppression is banned by the repo's classification contract (#6164). The classification helper for exactly this state already exists (requireProjectRepoPath, added in #6316); the create mutation was simply never given it.
Root cause
PR #6316 added requireProjectRepoPath — an ENOENT-safe statSync(..., { throwIfNoEntry: false })?.isDirectory() precheck that throws TRPCError NOT_FOUND with cause: { kind: "PROJECT_DIR_MISSING" } — and applied it to search-branches, list-project-worktrees and adopt, but not to workspaces.create or createEnqueued. The reporting middleware reports on INTERNAL_SERVER_ERROR alone, and createEnqueued drives the real create through a server-side caller whose middleware chain reports the escaping construct error even though the enqueue wrapper catches it afterwards.
Fix
Structural precheck only — no message matcher, matching the shape #6316 and #6634 established:
- In
workspaces.create, resolve the path throughrequireProjectRepoPath(localProject)once, immediately afterrequireLocalProject, and use the validated path for everything downstream that consumedlocalProject.repoPath(ensureMainWorkspace,ctx.git, the worker base-ref fetcher, fetchPrMetadata, both listBranchNames calls, applyGeneratedWorkspaceNames). - In
createEnqueued's cheap-validation layer, the same one-line guard, so a missing directory rejects synchronously with NOT_FOUND instead of enqueueing a create that is guaranteed to fail and settling with an error event. That layer already throws synchronous non-500s today (BAD_REQUEST for a missing id, PRECONDITION_FAILED for an unregistered project), so this adds no new client contract.
Typed cause: reuses the helper's existing PROJECT_DIR_MISSING as-is; nothing new is invented and no new reader is added.
What still reports, deliberately:
- A directory deleted between the precheck and the git call still surfaces as a 500 — that race is genuinely unexpected.
- A permission-walled path (EACCES/EPERM) still throws unclassified and keeps reporting:
throwIfNoEntry: falseswallows only ENOENT, and the new negative test pins that a permission error is NOT converted to NOT_FOUND (the over-match #6634's merge gate had to fix).
Adjacent sites left alone: workspaces.generateBranchName (and aiRename) also touch project.repoPath outside the create mutation; they have not appeared in this issue and are out of scope.
Verification
New tests written first and confirmed failing before the fix: the two NOT_FOUND tests failed (both paths returned 500s), and the EACCES negative pin passed pre-fix (stays INTERNAL_SERVER_ERROR before and after).
bunx biome check on the three changed files:
Checked 3 files in 17ms. No fixes applied.
cd packages/host-service && bun run typecheck:
$ tsc --noEmit --emitDeclarationOnly false
(no errors)
Full bun test test in packages/host-service:
1158 pass
8 todo
0 fail
2709 expect() calls
Ran 1166 tests across 113 files. [178.84s]
Co-located router suites (bun test src/trpc/router/workspaces src/trpc/router/workspace-creation):
167 pass
0 fail
Ran 167 tests across 14 files. [3.37s]
Refs HOST-SERVICE-W
https://claude.ai/code/session_01PWSqv5bU8VzRDHP8Z4iWqi
Summary by cubic
Classifies a missing project repo directory as NOT_FOUND during workspace creation to replace opaque 500s. Previously, workspaces.create and workspaces.createEnqueued threw INTERNAL_SERVER_ERROR from simple-git when the repo path was gone; now both precheck and return NOT_FOUND with cause PROJECT_DIR_MISSING, and the enqueued path rejects before scheduling.
- Validates the repo path once via
requireProjectRepoPathafterrequireLocalProjectinworkspaces.create, then uses the validated path for all downstream calls (main workspace ensure, git, base-ref fetcher, PR metadata, branch listing, name application). - Adds the same guard to
createEnqueuedso a missing directory rejects synchronously and is not enqueued. - Keeps intentional reporting: a directory deleted after the precheck still surfaces as a 500; permission errors (EACCES/EPERM) remain unclassified and report.
- Adds integration tests for NOT_FOUND on missing directories in both create paths and a negative pin that permission-walled paths stay INTERNAL_SERVER_ERROR.
- No API shape or schema changes; clients see deterministic NOT_FOUND instead of 500 on this condition. Addresses the classification gap tracked in HOST-SERVICE-W.
Summary by CodeRabbit
- Bug Fixes
- Workspace creation now validates that the project repository path exists before starting operations.
- Missing project directories return a clear “not found” error.
- Permission-related access failures are reported accurately instead of being treated as missing directories.
- Enqueued workspace creation no longer starts when the project directory is unavailable.