fix(desktop,host-service): translate expected errors at throw sites, keep the Sentry boundary dumb

Merged
SA
3mo ago
+1125-321

Why

Re-enabling the desktop Sentry key exposed ~140 events/hour that were never bugs: deleted worktrees, home-directories-registered-as-repos, vanished files — all thrown as generic Errors, escaping tRPC as INTERNAL_SERVER_ERROR, and reported. The top issue alone accumulated 359k events, and it was hiding a real product bug: the changes pane polls a permanently-gone worktree every 2.5s forever, spawning doomed git processes. An earlier attempt to fix this with name/errno allowlists in the Sentry middleware was rejected as classification-at-a-distance.

The contract

  • Expected domain states are typed non-500 TRPCErrors translated where context lives. Third-party/OS errors get translated once, at the adapter that calls them.
  • The Sentry middleware is dumb and loud: INTERNAL_SERVER_ERROR ⇒ report, zero allowlists. A 500 now means a missed translation at a throw site — fix the router, never add a filter.
  • Degraded is never silent: the renderer shows the state and pollers back off.

What changed

Desktop main

  • Adapters: simple-git construct failures, getStatusNoLock failures (maxBuffer/permissions/timeouts), and git worker timeouts become GitEnvironmentError (name-checked across the worker boundary); ensureWithinRoot throws WorkspaceFsPathError like its sibling guards.
  • path-validation.ts throws properly-coded TRPCErrors directly (UNREGISTERED_WORKTREE→404 etc.) — kills the deleted-workspace 500s at all ~33 call sites.
  • changes.getStatus/getBranches pre-check worktree existence (pathExistsCached) before spawning git tasks — no more doomed git spawns — and translate NotGitRepoError/GitEnvironmentError with causes.
  • Filesystem routers wrap service calls with an errno→TRPCError mapping (ENOENT→404, EISDIR→400, EACCES→403, ENOSPC→412; messages verbatim).
  • Bare-throw sweep: Project/Workspace-not-found families → NOT_FOUND; git lock contention, branch-already-checked-out, gh-not-installed/authed → PRECONDITION_FAILED; input validation → BAD_REQUEST; ui-state ENOSPC + browser-history SQLITE_FULL typed. Catch-all "unknown failure" wrappers deliberately stay 500.
  • New errorFormatter serializes plain-object {kind: ...} causes to the renderer (host-service pattern; verified the cause survives tRPC's UnknownCauseError wrapping).

Renderer

  • useGitChangesStatus surfaces error/errorCause; polling backs off to 30s on deterministic codes (400/403/404/412) and 10s on transient failures — refetchOnWindowFocus still gives instant recovery. Changes pane renders "Workspace folder no longer exists on disk" / "not a git repository" / "Git can't read this workspace" instead of a catch-all.
  • useFileContent branches on the FS_ERRNO cause instead of message.includes("EISDIR").
  • Renderer Sentry drops TRPCClientError events — the main-process middleware is the reporting source of truth.

Host-service (parity — previously reported nothing)

  • Same dumb Sentry middleware under both procedure chains; unhandledRejection now captured; errno translation in its filesystem router; typed WorkspaceNotFoundError/ProjectNotFoundError replace string-prefix matching.

Verification

  • bun run lint clean; typecheck clean (desktop, host-service, workspace-fs).
  • Tests: 288 desktop (incl. new backoff matrix in useGitChangesStatus.test.ts), 321 host-service, 58 workspace-fs — all pass.
  • Cause-serialization contract verified against tRPC's cause-wrapping behavior.

After ship (ops)

Takes effect as the fleet auto-updates to the next desktop release. Watch the desktop Sentry project: getStatus/getBranches clusters should go to ~0; host-service will report real 500s for the first time (expect a week-one triage wave). Any 500 carrying NotGitRepoError/GitEnvironmentError/WorkspaceFsPathError = missed translation arm to fix at the source.

https://claude.ai/code/session_012u6VZtfQgoTh8qS4NqCb7e


Summary by cubic

Translate expected desktop and host-service errors at throw sites into typed non-500 TRPCErrors with machine-readable causes, and keep the Sentry boundary “dumb” so only real 500s are reported. This cuts noisy events, stops endless git spawns on missing worktrees, and shows clear UI with smarter polling backoff.

  • Bug Fixes

    • Git: pre-check worktree existence in status/branches (and now getCommitFiles) and map NotGitRepoError/GitEnvironmentError to 400/412; worker now translates raw simple-git “not a git repository” into NotGitRepoError before crossing the boundary. worktreeExists ignores git prunable entries; getStatusNoLock classifies exec/timeout/maxBuffer as environment while porcelain parse failures surface as true 500s.
    • Filesystem/path/storage: map ENOENT/EISDIR/ENOTDIR/EACCES/EPERM/ENOSPC and path-guard failures to non-500s with FS_ERRNO/PATH_VALIDATION causes; browser history SQLITE_FULL and UI-state ENOSPC/EACCES/EPERM return typed errors.
    • Host-service: workspace teardown-script failures return 412 with a structured cause; unhandledRejection is captured; Sentry middleware reports only 500s.
    • UI: Changes pane shows “folder missing / not a git repo / Git can’t read” and backs off polling to 30s on deterministic errors; file viewer branches on FS_ERRNO; renderer Sentry drops duplicate TRPCClientErrors.
  • Refactors

    • All error translation lives in procedure bodies; utils throw domain errors only, and git-worker errors are rehydrated for instanceof checks. Added an errorFormatter to serialize kind-discriminated causes to the renderer.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling for missing workspaces, projects, worktrees, repositories, invalid paths, Git configuration issues, and filesystem failures.
    • Storage-full, permission, unavailable-resource, and workspace cleanup failures now provide clearer actionable feedback.
    • Git changes views distinguish unavailable states and display more relevant guidance.
    • File viewing more reliably identifies directory-access errors.
  • Performance

    • Git status polling backs off during persistent failures while continuing to retry transient issues.
  • Monitoring

    • Improved reporting of unexpected service and Git failures while filtering handled client errors.