{"record":{"id":"b028fc39eaef164b","repo":"different-ai/openwork","slug":"delete-mcp-connection-response-was-incomplete","errorCode":null,"errorMessage":"Delete MCP connection response was incomplete.","messagePattern":"Delete MCP connection 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":972,"sourceCode":"export function useDeleteMcpConnection() {\n  const queryClient = useQueryClient();\n  const { orgId, runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (connectionId: string): Promise<string> => {\n      let result: string | null = null;\n      await runReauthableAction(\"delete-mcp-connection\", async () => {\n        const { response, payload } = await requestJson(\n          `/v1/mcp-connections/${encodeURIComponent(connectionId)}`,\n          { method: \"DELETE\", headers: getOrgScopeHeaders(requireOrgId(orgId)) },\n          15000,\n        );\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Failed to remove MCP connection (${response.status}).`);\n        }\n        result = connectionId;\n      });\n      if (!result) throw new Error(\"Delete MCP connection response was incomplete.\");\n      return result;\n    },\n    onSuccess: () => {\n      queryClient.invalidateQueries({ queryKey: mcpConnectionQueryKeys.all });\n    },\n  });\n}\n\nexport type SaveNativeProviderClientInput = {\n  providerId: string;\n  clientId?: string;\n  clientSecret?: string;\n  tenantId?: string;\n  features: string[];\n};\n\nexport type NativeProviderClient = {\n  providerId: string;","sourceCodeStart":954,"sourceCodeEnd":990,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx#L954-L990","documentation":"The delete (remove) MCP connection mutation assigns `result = connectionId` in its runReauthableAction callback after a 2xx remove response. This guard throws when that assignment never happened, protecting callers from a mutation resolving with an undefined id while onSuccess cache invalidation assumes a completed delete.","triggerScenarios":"mutateAsync delete with falsy connectionId, or runReauthableAction resolving without re-executing the callback after a dismissed or failed re-auth.","commonSituations":"Double-delete race: first click removed the row and cleared its id; session re-auth interrupted the flow; wrapper refactor changed semantics.","solutions":["Guard the UI so delete can't be invoked without a valid connectionId","Confirm runReauthableAction always runs the callback exactly once and throws on auth failure","Add logging in the callback to verify the remove fetch succeeded and assignment ran","Check onSuccess invalidation still runs — on failure the list query may show a stale row; invalidate manually in the catch path"],"exampleFix":"// before\nif (!result) throw new Error(\"Delete MCP connection response was incomplete.\");\n// after\nif (!result) {\n  throw new Error(`Delete MCP connection: no connectionId assigned (input=${JSON.stringify(connectionId)}).`);\n}","handlingStrategy":"validation","validationCode":"if (typeof connectionId !== 'string' || connectionId.length === 0) {\n  throw new Error('cannot delete: connectionId missing');\n}","typeGuard":"function isDeleteInput(v: unknown): v is { connectionId: string } {\n  return isRecord(v) && typeof v.connectionId === 'string' && v.connectionId.length > 0;\n}","tryCatchPattern":"try {\n  await removeConnection.mutateAsync(connectionId);\n} catch (e) {\n  showToast({ variant: 'error', title: 'Delete failed', description: e instanceof Error ? e.message : String(e) });\n  queryClient.invalidateQueries({ queryKey: mcpConnectionQueryKeys.all });\n}","preventionTips":["Prevent double-submit deletes (disable button while isPending)","Refetch the list when a delete errors so removed rows disappear","Keep wrapper semantics: callback runs exactly once or throws","Verify the remove endpoint returns a JSON body on success"],"tags":["react-query","mutation","null-guard"],"backgroundTag":"empty-api-response","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}