{"record":{"id":"7b30a09240250ea1","repo":"mastra-ai/mastra","slug":"pull-request-subscriptions-returned-an-invalid-res","errorCode":null,"errorMessage":"Pull request subscriptions returned an invalid response.","messagePattern":"Pull request subscriptions returned an invalid response\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/factory/services/githubSubscriptions.ts","lineNumber":51,"sourceCode":"    isPullRequestStatus(value.status) &&\n    typeof value.url === 'string'\n  );\n}\n\nexport async function listPullRequestSubscriptions(\n  baseUrl: string,\n  resourceId: string,\n  threadId: string,\n  projectPath?: string,\n): Promise<PullRequestSubscription[]> {\n  const params = new URLSearchParams({ resourceId, threadId });\n  if (projectPath) params.set('scope', projectPath);\n  const response = await fetch(`${baseUrl}/web/github/subscriptions?${params}`, { credentials: 'include' });\n  if (!response.ok) throw new Error(`Failed to load pull request subscriptions (${response.status}).`);\n\n  const body: unknown = await response.json();\n  if (!isRecord(body) || !Array.isArray(body.subscriptions)) {\n    throw new Error('Pull request subscriptions returned an invalid response.');\n  }\n  // one bad row must not hide every other pull request; warn so a widened server enum is not silent\n  const subscriptions = body.subscriptions.filter(isPullRequestSubscription);\n  const dropped = body.subscriptions.length - subscriptions.length;\n  if (dropped > 0 && import.meta.env.DEV) {\n    console.warn(`Dropped ${dropped} pull request subscription(s) the client does not understand.`);\n  }\n  return subscriptions;\n}\n","sourceCodeStart":33,"sourceCodeEnd":61,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/factory/services/githubSubscriptions.ts#L33-L61","documentation":"After a successful HTTP fetch of pull request subscriptions, the client validates the JSON body: it must be an object with a 'subscriptions' array. If not, it throws 'Pull request subscriptions returned an invalid response.' This guards against contract drift between server and client instead of crashing later on malformed rows.","triggerScenarios":"The endpoint returned 200 but with a body that is not a record or lacks a 'subscriptions' array: a proxy/login page returning HTML, an error JSON like {\"error\":\"...\"} with 200 status, an API version change renaming/restructuring the field, or an empty/malformed response body.","commonSituations":"Auth middleware intercepts and returns 200 HTML, server deployed with an older/newer response shape ({\"data\":{...}} instead of {\"subscriptions\":[...]}), or a dev proxy returning an index.html fallback for unknown routes.","solutions":["Log/inspect the actual response body to see what the server returned","Confirm the server version matches the client's expected shape ({ subscriptions: [...] })","Check that the request is not being answered by an HTML page (auth redirect or SPA fallback) instead of the API","Upgrade or align the server route with the factory-ui client contract"],"exampleFix":"// before\n// server: res.json({ data: subs })\n// after\n// server: res.json({ subscriptions: subs })","handlingStrategy":"type-guard","validationCode":"const res = await fetch(url, { credentials: 'include' });\nconst text = await res.text();\nif (!text.trim().startsWith('{')) throw new Error('Expected JSON but got: ' + text.slice(0, 80)); // catches HTML proxy/login fallbacks","typeGuard":"function isSubscriptionsResponse(v: unknown): v is { subscriptions: unknown[] } {\n  return typeof v === 'object' && v !== null && 'subscriptions' in v && Array.isArray((v as { subscriptions: unknown }).subscriptions);\n}","tryCatchPattern":"try {\n  const subs = await listPullRequestSubscriptions(baseUrl, resourceId, threadId);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('invalid response')) {\n    // contract drift or HTML response: log raw payload and show empty state\n    console.error('Subscriptions response shape mismatch', err);\n    renderEmptyState();\n  } else throw err;\n}","preventionTips":["Pin/align client and server response contracts; add a shared schema/type import","Check for auth middleware that returns 200 HTML instead of redirecting","Add an integration test asserting the endpoint's exact JSON shape","Disable SPA/HTML fallbacks for /web/api-style routes in dev proxies"],"tags":["http","validation","contract-mismatch","github-subscriptions"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}