{"record":{"id":"d687072a260fb5e0","repo":"mastra-ai/mastra","slug":"body-error-or-request-failed-res-status","errorCode":null,"errorMessage":"${body.error} or Request failed (${res.status})","messagePattern":"(.+?) or Request failed \\((.+?)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/factory/services/workItems.ts","lineNumber":220,"sourceCode":"\nexport async function transitionWorkItem(\n  baseUrl: string,\n  githubProjectId: string,\n  id: string,\n  input: { board: FactoryBoard; stage: FactoryRuleStage; expectedRevision: number; requestId: string; cause: string },\n): Promise<FactoryTransitionResult> {\n  const res = await fetch(\n    `${baseUrl}/web/factory/projects/${encodeURIComponent(githubProjectId)}/work-items/${encodeURIComponent(id)}/transition`,\n    {\n      method: 'POST',\n      headers: { Accept: 'application/json', 'content-type': 'application/json' },\n      credentials: 'include',\n      body: JSON.stringify(input),\n    },\n  );\n  const body = (await res.json()) as { result?: FactoryTransitionResult; error?: string };\n  if (body.result) return body.result;\n  throw new Error(body.error ?? `Request failed (${res.status})`);\n}\n\n/** Patch a work item's non-stage metadata, session refs, or title. */\nexport async function updateWorkItem(baseUrl: string, id: string, patch: UpdateWorkItemInput): Promise<WorkItem> {\n  const data = await requestJson<{ workItem: WireWorkItem }>(\n    `${baseUrl}/web/factory/work-items/${encodeURIComponent(id)}`,\n    { method: 'PATCH', body: JSON.stringify(patch) },\n  );\n  return fromWireWorkItem(data.workItem);\n}\n\nexport interface StartFactoryRunRequest {\n  sessionId: string;\n  threadTitle: string;\n  threadTags?: Record<string, string>;\n  kickoffKey: string;\n  invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };\n  destinationStage: FactoryRuleStage;","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/factory/services/workItems.ts#L202-L238","documentation":"transitionWorkItem posts a stage transition for a factory work item and parses the response as `{ result?, error? }`. If the server did not return a result, it throws `new Error(body.error ?? 'Request failed (status)')` — either the backend's transition error text or a generic status-based message. This is the client-side representation of a rejected (or failed) work-item transition.","triggerScenarios":"Calling transitionWorkItem (from a mutation) when: the target stage is invalid for the item's current stage (server sends body.error), the item was concurrently modified, the session is unauthorized, or the response body is neither a result nor an error (e.g. non-JSON 500 page), producing `Request failed (${res.status})`.","commonSituations":"Two users/agents transitioning the same work item at once (stale stage), attempting an illegal stage jump the backend state machine forbids, expired session cookie returning 401, or a gateway error page instead of JSON.","solutions":["Read body.error from the thrown message — it names the exact transition rule violated; adjust the target stage accordingly.","Re-fetch the work item to get its current stage before retrying the transition (handles concurrent-change conflicts).","If the message is the generic 'Request failed (status)', check the status code: 401 -> re-authenticate, 500 -> inspect server logs.","Ensure the transition input payload matches the API's expected shape (missing fields can cause silent 400s without a useful error field)."],"exampleFix":"// before\nawait transitionWorkItem(baseUrl, id, { toStage: 'done' });\n// after\ntry {\n  await transitionWorkItem(baseUrl, id, { toStage: 'done' });\n} catch (e) {\n  const fresh = await fetchWorkItem(baseUrl, id); // re-sync current stage\n  await transitionWorkItem(baseUrl, id, { toStage: nextValidStage(fresh.stage) });\n}","handlingStrategy":"try-catch","validationCode":"// validate the transition locally against the item's current stage\nconst allowed = ['backlog', 'in_progress', 'review', 'done'];\nconst fromIdx = allowed.indexOf(workItem.stage);\nconst toIdx = allowed.indexOf(input.toStage);\nif (toIdx < 0) throw new Error(`Unknown stage: ${input.toStage}`);\nif (toIdx < fromIdx) throw new Error('Backward transition requires explicit confirmation');","typeGuard":"type TransitionResponse = { result?: FactoryTransitionResult; error?: string };\nfunction hasResult(b: TransitionResponse): b is { result: FactoryTransitionResult; error?: string } {\n  return typeof b === 'object' && b !== null && 'result' in b && b.result != null;\n}","tryCatchPattern":"const mutation = useMutation({\n  mutationFn: () => transitionWorkItem(baseUrl, id, input),\n  onError: (e: Error) => {\n    if (e.message.includes('Request failed (')) {\n      // no server error text: refresh and retry once\n      queryClient.invalidateQueries(['work-item', id]);\n    } else {\n      showSnackbar(e.message); // server-provided transition rule error\n    }\n  },\n});","preventionTips":["Re-fetch the work item right before transitioning to avoid stale-stage conflicts.","Model valid stage transitions client-side to catch illegal moves before the request.","Surface the server's body.error verbatim to users — it explains the rejected transition.","Handle concurrent edits (optimistic locking or invalidate-and-retry) in the mutation."],"tags":["http-error","state-transition","api-response","work-items"],"backgroundTag":"invalid-state-transition","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}