{"record":{"id":"743519e8ae4e4fe0","repo":"lobehub/lobehub","slug":"not-found-743519","errorCode":"NOT_FOUND","errorMessage":"Connector not found","messagePattern":"Connector not found","errorType":"exception","errorClass":"TRPCError","httpStatus":404,"severity":"error","filePath":"apps/server/src/routers/lambda/connector.ts","lineNumber":264,"sourceCode":"\n  /**\n   * Return the connector record with decrypted user-set credentials so the\n   * edit form can pre-fill accurately. Only the connector owner can call this\n   * (enforced by connectorProcedure ownership check).\n   *\n   * Machine-managed secrets are intentionally excluded:\n   * - OAuth access/refresh tokens (type 'oauth2') → stripped, returned as null\n   * - oidcConfig.clientSecret (DCR-registered secret)  → stripped\n   * User-set credentials (bearer token, custom headers) are returned as-is so\n   * the edit form can display them.\n   */\n  // Member-gated even though it's a query: it returns decrypted credentials\n  // for edit prefill, so a creator later downgraded to viewer must not reach it.\n  getForEdit: connectorWriteProcedure\n    .input(z.object({ id: z.string().uuid() }))\n    .query(async ({ input, ctx }) => {\n      const connector = await ctx.connectorModel.findById(input.id);\n      if (!connector) throw new TRPCError({ code: 'NOT_FOUND', message: 'Connector not found' });\n      // Edit-read returns decrypted credentials — same creator/owner gate as\n      // the mutations that this edit view feeds.\n      assertWorkspaceRowManageable(ctx, connector.userId, 'connector');\n\n      const { oidcConfig, credentials, ...rest } = connector;\n      const safeOidcConfig = oidcConfig ? { ...oidcConfig, clientSecret: undefined } : oidcConfig;\n      // OAuth tokens are machine-managed — don't return them; the UI only needs\n      // to know an OAuth flow is configured (reflected via oidcConfig presence).\n      const safeCredentials = credentials?.type === 'oauth2' ? null : credentials;\n\n      return { ...rest, credentials: safeCredentials, oidcConfig: safeOidcConfig };\n    }),\n\n  /**\n   * The exact redirect URI the server will send to the OAuth/DCR endpoints.\n   * The Add modal must display THIS value (not a client-derived origin) so the\n   * URI the user registers matches the one used at authorize time.\n   */","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/lobehub/lobehub/blob/10f24d7ade75139093a9373b364f6bc91f3cd7db/apps/server/src/routers/lambda/connector.ts#L246-L282","documentation":"Thrown by `connector.getForEdit` when `ctx.connectorModel.findById(input.id)` returns null. The input is validated as a UUID, so this is a genuine 'no such connector (in the caller's scope)' condition. Because `findById` is workspace/user-scoped, a connector owned by another user/workspace is invisible and reads as not-found. A viewer-role caller is blocked earlier by `connectorWriteProcedure`; this check fires for an owner whose connector was deleted or whose scope is wrong.","triggerScenarios":"Opening the connector edit form for an id deleted since the list loaded, or for a connector in a different workspace. A creator later downgraded to viewer is stopped at the write-procedure gate first.","commonSituations":"Stale edit deep-link; concurrent deletion of the connector; switching workspace while the edit modal is open.","solutions":["Confirm the id is from a fresh `connector.list` response for the current workspace.","On the client, treat NOT_FOUND from getForEdit by closing the edit form and refreshing the list.","Ensure the workspace scope in the request matches the connector's workspace.","If the connector was shared, verify the caller is still its owner (downgrade to viewer is blocked earlier)."],"exampleFix":"// before\nconst connector = await trpc.connector.getForEdit.query({ id });\n\n// after\ntry { const connector = await trpc.connector.getForEdit.query({ id }); }\ncatch (e) {\n  if (e.data?.code === 'NOT_FOUND') { closeEditModal(); refreshList(); }\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Confirm the connector is still in the current workspace list\nconst list = await trpc.connector.list.query();\nif (!list.some((c) => c.id === id)) { closeEditModal(); return; }","typeGuard":"const isTrpcNotFound = (e: unknown): boolean =>\n  typeof e === 'object' && e !== null && (e as any).data?.code === 'NOT_FOUND';","tryCatchPattern":"try {\n  await trpc.connector.getForEdit.query({ id });\n} catch (e) {\n  if (isTrpcNotFound(e)) { closeEditModal(); refreshList(); return; }\n  throw e;\n}","preventionTips":["Source edit-form ids from a fresh `connector.list` for the current workspace.","Handle NOT_FOUND in the edit modal by closing and refreshing.","Reopen the edit form only after confirming the caller is still the connector owner."],"tags":["trpc","not-found","connector","database","scoped-query","rbac"],"backgroundTag":null,"analyzedSha":"10f24d7ade75139093a9373b364f6bc91f3cd7db","analyzedAt":"2026-08-12T11:43:19.543Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}