mastra-ai/mastra · error
${body.error} or Request failed (${res.status})
Error message
${body.error} or Request failed (${res.status}) What it means
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.
Source
Thrown at mastracode/factory-ui/src/ui/domains/factory/services/workItems.ts:220
export async function transitionWorkItem(
baseUrl: string,
githubProjectId: string,
id: string,
input: { board: FactoryBoard; stage: FactoryRuleStage; expectedRevision: number; requestId: string; cause: string },
): Promise<FactoryTransitionResult> {
const res = await fetch(
`${baseUrl}/web/factory/projects/${encodeURIComponent(githubProjectId)}/work-items/${encodeURIComponent(id)}/transition`,
{
method: 'POST',
headers: { Accept: 'application/json', 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
},
);
const body = (await res.json()) as { result?: FactoryTransitionResult; error?: string };
if (body.result) return body.result;
throw new Error(body.error ?? `Request failed (${res.status})`);
}
/** Patch a work item's non-stage metadata, session refs, or title. */
export async function updateWorkItem(baseUrl: string, id: string, patch: UpdateWorkItemInput): Promise<WorkItem> {
const data = await requestJson<{ workItem: WireWorkItem }>(
`${baseUrl}/web/factory/work-items/${encodeURIComponent(id)}`,
{ method: 'PATCH', body: JSON.stringify(patch) },
);
return fromWireWorkItem(data.workItem);
}
export interface StartFactoryRunRequest {
sessionId: string;
threadTitle: string;
threadTags?: Record<string, string>;
kickoffKey: string;
invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };
destinationStage: FactoryRuleStage;View on GitHub (pinned to 75dd419e61)
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).
Example fix
// before
await transitionWorkItem(baseUrl, id, { toStage: 'done' });
// after
try {
await transitionWorkItem(baseUrl, id, { toStage: 'done' });
} catch (e) {
const fresh = await fetchWorkItem(baseUrl, id); // re-sync current stage
await transitionWorkItem(baseUrl, id, { toStage: nextValidStage(fresh.stage) });
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate the transition locally against the item's current stage
const allowed = ['backlog', 'in_progress', 'review', 'done'];
const fromIdx = allowed.indexOf(workItem.stage);
const toIdx = allowed.indexOf(input.toStage);
if (toIdx < 0) throw new Error(`Unknown stage: ${input.toStage}`);
if (toIdx < fromIdx) throw new Error('Backward transition requires explicit confirmation'); Type guard
type TransitionResponse = { result?: FactoryTransitionResult; error?: string };
function hasResult(b: TransitionResponse): b is { result: FactoryTransitionResult; error?: string } {
return typeof b === 'object' && b !== null && 'result' in b && b.result != null;
} Try / catch
const mutation = useMutation({
mutationFn: () => transitionWorkItem(baseUrl, id, input),
onError: (e: Error) => {
if (e.message.includes('Request failed (')) {
// no server error text: refresh and retry once
queryClient.invalidateQueries(['work-item', id]);
} else {
showSnackbar(e.message); // server-provided transition rule error
}
},
}); Prevention
- 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.
When it happens
Trigger: 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})`.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Request failed (${res.status}) / server-provided message
- Failed to fetch CDP version info from ${versionUrl}: ${respo
- Failed to update Edge Config alias "${options.key}" (${res.s
- No relevance score found in VoyageAI response
- Related work item not found in this project.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d687072a260fb5e0.
Report an issue: GitHub.