{"record":{"id":"d57c84d0a447b293","repo":"mastra-ai/mastra","slug":"request-failed-response-status-server-provi","errorCode":null,"errorMessage":"Request failed (${response.status}) / server-provided message","messagePattern":"Request failed \\((.+?)\\) / server-provided message","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/factory/services/decisions.ts","lineNumber":48,"sourceCode":"  createdAt: string;\n  updatedAt: string;\n  completedAt: string | null;\n}\n\nexport interface FactoryDecisionPage {\n  decisions: FactoryDecisionSummary[];\n  nextCursor?: string;\n}\n\nasync function throwRequestError(response: Response): Promise<never> {\n  let message = `Request failed (${response.status})`;\n  try {\n    const body = (await response.json()) as { error?: string; message?: string };\n    message = body.message ?? body.error ?? message;\n  } catch {\n    // Keep the status-based fallback for non-JSON responses.\n  }\n  throw new Error(message);\n}\n\nexport async function fetchFactoryDecisions(\n  baseUrl: string,\n  githubProjectId: string,\n  options: { statuses?: FactoryDecisionStatus[]; before?: string; limit?: number } = {},\n): Promise<FactoryDecisionPage> {\n  const query = new URLSearchParams();\n  if (options.statuses?.length) query.set('statuses', options.statuses.join(','));\n  if (options.before) query.set('before', options.before);\n  if (options.limit) query.set('limit', String(options.limit));\n  const suffix = query.size > 0 ? `?${query}` : '';\n  const response = await fetch(\n    `${baseUrl}/web/factory/projects/${encodeURIComponent(githubProjectId)}/decisions${suffix}`,\n    { headers: { Accept: 'application/json' }, credentials: 'include' },\n  );\n  if (!response.ok) return throwRequestError(response);\n  return (await response.json()) as FactoryDecisionPage;","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/factory/services/decisions.ts#L30-L66","documentation":"fetchFactoryDecisions/actOnFactoryDecision hit the factory decisions API and throwRequestError converts any non-OK HTTP response into a thrown Error. The message prefers the server-provided body message/error field and falls back to a generic 'Request failed (<status>)'. This surfaces backend rejection (auth, bad params, server error) to the UI layer.","triggerScenarios":"Any response with response.ok === false from GET factory decisions (with statuses/before/limit query params) or POST/PATCH actOnFactoryDecision, including 401/403 auth failures, invalid githubProjectId, bad query params (limit out of range, malformed before cursor), or 5xx server errors.","commonSituations":"Expired or missing session cookie (credentials), requesting decisions with a status enum value the server no longer accepts after a version change, paginating past the last page with an invalid 'before' cursor, or the factory server being down (502/503).","solutions":["Inspect error.message: if it contains the server message, fix the request it describes; otherwise check response status via a network tab","Verify the user session/auth cookie is valid for the factory baseUrl","Validate statuses/before/limit values against the current FactoryDecisionStatus enum and API limits","Retry after confirming the factory backend is healthy if status is 5xx"],"exampleFix":"// before\nconst decisions = await fetchFactoryDecisions(baseUrl, projectId, { statuses: ['unknown-status'] });\n// after\nconst decisions = await fetchFactoryDecisions(baseUrl, projectId, { statuses: ['pending'] satisfies FactoryDecisionStatus[] });","handlingStrategy":"try-catch","validationCode":"const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;\nif (!isNonEmptyString(baseUrl) || !isNonEmptyString(githubProjectId)) throw new Error('baseUrl and githubProjectId are required');\nconst validStatuses = new Set<string>(['pending', 'approved', 'rejected']); // keep in sync with FactoryDecisionStatus\nif (options.statuses && !options.statuses.every(s => validStatuses.has(s))) throw new Error('Invalid status filter');","typeGuard":"function isFactoryDecisionStatus(v: unknown): v is FactoryDecisionStatus {\n  return typeof v === 'string' && ['pending', 'approved', 'rejected'].includes(v);\n}","tryCatchPattern":"try {\n  const decisions = await fetchFactoryDecisions(baseUrl, githubProjectId, { statuses });\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  if (/401|403/.test(msg)) showLoginPrompt();\n  else if (/5\\d\\d/.test(msg)) retryLater();\n  else showError(msg);\n}","preventionTips":["Keep the client FactoryDecisionStatus enum in sync with the server enum","Always send credentials: 'include' and handle 401 by redirecting to login","Validate limit/before pagination parameters before calling","Wrap all factory service calls in a shared error boundary that parses the status from the message"],"tags":["http","api","factory-decisions"],"backgroundTag":"http-request-failed-with-status","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}