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

  1. Read body.error from the thrown message — it names the exact transition rule violated; adjust the target stage accordingly.
  2. Re-fetch the work item to get its current stage before retrying the transition (handles concurrent-change conflicts).
  3. If the message is the generic 'Request failed (status)', check the status code: 401 -> re-authenticate, 500 -> inspect server logs.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/d687072a260fb5e0. Report an issue: GitHub.