fix(host-service): classify missing cloud project in setup as NOT_FOUND
Problem
project.setup files a steady trickle of INTERNAL_SERVER_ERROR reports (HOST-SERVICE-16, 14 events lifetime, 2 in the last 24h, release 1.20.2). The captured stack contains only tRPC client transport frames (TRPCClientError.from), which is why the first-party site was hard to place from the event alone; the trpc_path tag project.setup is what identifies it.
Root cause
Setting a project up on a device falls back to a cloud lookup when the project is neither already in the local host database nor accompanied by caller-supplied origin coordinates:
const cloudProject =
existing || input.origin
? null
: await ctx.api.v2Project.get.query({ organizationId: ctx.organizationId, id: input.projectId });
When the cloud has no such project — the ordinary outcome for a project deleted cloud-side, or for an older client trying to adopt one it cannot see — upstream v2Project.get rejects NOT_FOUND ("Project not found") via requireOrgScopedResource. Nothing at this boundary caught it, so the rejection propagated out of the mutation and the Sentry middleware, which reports every INTERNAL_SERVER_ERROR reaching it, filed it as a bug. This is an expected domain state, not a bug, so the missing classification belongs at the throw site.
Fix
Wrap that one upstream call in fetchCloudProjectForSetup. An upstream not-found is rethrown as a typed TRPCError with code NOT_FOUND and cause { kind: "CLOUD_PROJECT_NOT_FOUND" }, so the Sentry middleware never sees a 500. Everything else — network failures, cloud 5xx, auth errors — propagates untouched and keeps reporting.
Detection is a name check (error.name === "TRPCClientError"), not instanceof: errors arriving from the upstream client cross a bundle boundary and lose their prototype. The not-found condition matches the structured data.code, falling back to a message check only when no structured code is present. The upstream message text is preserved verbatim, so user-facing copy is unchanged. Behaviour is otherwise identical — this is a classification change only.
No Sentry-side suppression, no beforeSend filter, no middleware allowlist.
Verification
bunx biome check packages/host-service/src/trpc/router/project/project.ts:
Checked 1 file in 11ms. No fixes applied.
cd packages/host-service && bun run typecheck:
$ tsc --noEmit --emitDeclarationOnly false
bun test src/trpc/router/project/project-import.test.ts (existing tests over the touched router):
bun test v1.3.14 (0d9b296a)
3 pass
0 fail
16 expect() calls
Ran 3 tests across 1 file. [1159.00ms]
Refs HOST-SERVICE-16
https://claude.ai/code/session_70334aac-77cb-4a5f-86c0-8cd388bf20a0
Summary by cubic
Classifies a missing cloud project during setup as NOT_FOUND instead of INTERNAL_SERVER_ERROR, reducing Sentry noise and returning a typed not-found to clients. Old behavior: unhandled upstream NOT_FOUND bubbled as 500. New behavior: TRPCError with code NOT_FOUND and cause { kind: "CLOUD_PROJECT_NOT_FOUND" }. No user-facing copy change.
- Wrap the cloud lookup in
fetchCloudProjectForSetupinpackages/host-service, rethrowing a typed NOT_FOUND. - Detect upstream not-found via
error.name === "TRPCClientError"anddata.code === "NOT_FOUND", with a message fallback; preserve upstream message. - Scope: only the setup path when the project is not in the local DB and no
originis provided. Network/auth/5xx still propagate and report.
Summary by CodeRabbit
- Bug Fixes
- Improved handling of missing cloud projects during legacy setup lookups.
- Missing projects are now consistently reported as “Not Found,” while unrelated errors continue to be surfaced unchanged.