{"record":{"id":"dc2cfe2bbad3450e","repo":"different-ai/openwork","slug":"oauth-issuer-review-response-was-incomplete","errorCode":null,"errorMessage":"OAuth issuer review response was incomplete.","messagePattern":"OAuth issuer review response was incomplete\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx","lineNumber":838,"sourceCode":"          `/v1/mcp-connections/${encodeURIComponent(connectionId)}/oauth/issuer-review`,\n          {\n            method: \"POST\",\n            headers: getOrgScopeHeaders(requireOrgId(orgId)),\n            body: JSON.stringify(body),\n          },\n          30000,\n        );\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Failed to review the OAuth issuer (${response.status}).`);\n        }\n        review = payload as McpIssuerReview;\n      };\n      if (input.action === \"confirm\") {\n        await runReauthableAction(\"review-mcp-oauth-issuer\", request);\n      } else {\n        await request();\n      }\n      if (!review) throw new Error(\"OAuth issuer review response was incomplete.\");\n      return review;\n    },\n    onSuccess: (_review, input) => {\n      if (input.action === \"confirm\") {\n        queryClient.invalidateQueries({ queryKey: mcpConnectionQueryKeys.all });\n      }\n    },\n  });\n}\n\nexport function useReplaceMcpConnectionAccess() {\n  const queryClient = useQueryClient();\n  const { orgId, runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (input: { connectionId: string; access: McpConnectionAccessInput }): Promise<string> => {\n      let result: string | null = null;\n      await runReauthableAction(\"replace-mcp-connection-access\", async () => {","sourceCodeStart":820,"sourceCodeEnd":856,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx#L820-L856","documentation":"The OAuth issuer review mutation calls runReauthableAction (confirm) or request() directly (other actions) and stores the result in `review`. If neither path assigns a value — e.g. the wrapper resolved without executing the request — this guard throws. It prevents the mutation from resolving with an undefined review record.","triggerScenarios":"mutateAsync({action:'confirm'|'reject', ...}) where runReauthableAction('review-mcp-oauth-issuer', request) resolves without invoking `request` (re-auth cancelled/skipped) or `request()` itself resolves without populating `review`.","commonSituations":"Expired Den session during an OAuth issuer security review; user re-auth flow was dismissed by the wrapper; a change to runReauthableAction changed its return contract.","solutions":["Confirm runReauthableAction returns the callback's value after re-auth and doesn't resolve undefined when re-auth is dismissed","Log inside `request` to verify the fetch runs and assigns `review`","Handle the dismissed-reauth case in UI (catch and surface 'review not completed' instead of a raw error)","Check the review endpoint returns the review object with 2xx"],"exampleFix":"// before\nif (!review) throw new Error(\"OAuth issuer review response was incomplete.\");\n// after\nif (!review) {\n  throw new Error(`OAuth issuer review (${input.action}) returned no result; re-auth may have been dismissed.`);\n}","handlingStrategy":"try-catch","validationCode":"const canReview = typeof issuerId === 'string' && ['confirm','reject'].includes(action);","typeGuard":"function isOAuthIssuerReview(v: unknown): v is OAuthIssuerReview {\n  return isRecord(v) && typeof (v as Record<string, unknown>).issuerId === 'string';\n}","tryCatchPattern":"try {\n  await reviewIssuer.mutateAsync({ action, issuerId });\n} catch (e) {\n  if (isReauthDismissedError(e)) { promptSignIn(); return; }\n  showToast({ variant: 'error', title: 'OAuth issuer review failed', description: e instanceof Error ? e.message : String(e) });\n}","preventionTips":["Surface re-auth prompts modally so they cannot be silently dismissed mid-mutation","Validate action/issuerId inputs before mutateAsync","Test the dismissed-reauth path explicitly","Invalidate mcpConnectionQueryKeys.all in onError too, so stale review state never lingers"],"tags":["react-query","oauth","mutation","null-guard"],"backgroundTag":"empty-api-response","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}