{"record":{"id":"695c024bc6301744","repo":"mastra-ai/mastra","slug":"error-message-422-res-body-includes-attempted-off","errorCode":null,"errorMessage":"error.message (422 res body includes attempted/offendingLabel for workflow label validation)","messagePattern":"error\\.message \\(422 res body includes attempted/offendingLabel for workflow label validation\\)","errorType":"http","errorClass":"HTTPException","httpStatus":422,"severity":"error","filePath":"packages/server/src/server/handlers/error.ts","lineNumber":107,"sourceCode":"}\n\n// Helper to handle errors consistently\nexport function handleError(error: unknown, defaultMessage: string): never {\n  if (isModelNotAllowedError(error)) {\n    const body = {\n      error: {\n        code: error.code,\n        message: error.message,\n        allowed: error.allowed,\n        attempted: error.attempted,\n        offendingLabel: error.offendingLabel,\n      },\n    };\n    const res = new Response(JSON.stringify(body), {\n      status: 422,\n      headers: { 'content-type': 'application/json' },\n    });\n    throw new HTTPException(422, {\n      res,\n      message: error.message,\n      cause: error,\n    });\n  }\n\n  // A losing concurrent resume is a conflict on run state, not a malformed request, so it maps\n  // to 409 and clients can distinguish it from a 400/500 and re-read the run.\n  if (isWorkflowResumeAlreadyClaimedError(error)) {\n    throw new HTTPException(409, {\n      message: error.message,\n      stack: error.stack,\n      cause: error,\n    });\n  }\n\n  if (isWorkflowSchemaValidationError(error)) {\n    throw new HTTPException(400, {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/error.ts#L89-L125","documentation":"handleError maps a ModelNotAllowedError (code MODEL_NOT_ALLOWED, from @mastra/core/agent-builder/ee) thrown by agent-builder routes into an HTTPException with status 422 and a JSON body carrying code, message, allowed, attempted, and offendingLabel. The library throws this because the requested model (or model label) is not on the allowed model policy for the agent builder, so the request is semantically invalid even though the request shape is fine. The 422 body is structured so clients can show exactly which label/model was rejected and what is allowed.","triggerScenarios":"Calling LIST_AGENT_BUILDER_ACTIONS_ROUTE, GET_AGENT_BUILDER_ACTION_BY_ID_ROUTE, LIST_AGENT_BUILDER_ACTION_RUNS_ROUTE, GET_AGENT_BUILDER_ACTION_RUN_BY_ID_ROUTE, CREATE_AGENT_BUILDER_ACTION_RUN_ROUTE, or STREAM_AGENT_BUILDER_ACTION_ROUTE when the action references a model or label that fails the model allowlist policy (offendingLabel present in the 422 response body).","commonSituations":"A builder action was created with a model that a later model-policy change removed from the allowlist; an org restricts models by label (e.g. only 'small'/'large' tiers) and a run selects a disallowed label; bundled @mastra/server resolves against a core version whose agent-builder policy differs from what the UI expects.","solutions":["Read the 422 response body's error.attempted and error.offendingLabel plus error.allowed to see which model/label was rejected and switch the action to an allowed model.","Update the agent-builder action's model field to one of the models in the allowed list from the error body.","If the model should be permitted, adjust the server-side model policy/allowlist configuration to include that label or model ID.","Check for a version mismatch between @mastra/server and @mastra/core (<1.34.0 lacks the agent-builder/ee subpath) and align versions."],"exampleFix":"// before: run created with a disallowed model label\nawait client.getAgentBuilderActionRun(actionId, { model: 'gpt-4-internal' });\n// after: use a model allowed by the policy (returned in error.allowed)\nawait client.getAgentBuilderActionRun(actionId, { model: 'openai/gpt-4o' });","handlingStrategy":"try-catch","validationCode":"const allowedModels = new Set(['openai/gpt-4o', 'anthropic/claude-sonnet-4']);\nif (!allowedModels.has(action.model)) {\n  throw new Error(`Model ${action.model} is not on the allowlist`);\n}","typeGuard":"function isModelNotAllowedBody(body: unknown): body is { error: { code: 'MODEL_NOT_ALLOWED'; allowed?: unknown; attempted?: unknown; offendingLabel?: string } } {\n  return !!body && typeof body === 'object' && (body as any).error?.code === 'MODEL_NOT_ALLOWED';\n}","tryCatchPattern":"try {\n  const run = await createAgentBuilderActionRun(actionId, input);\n} catch (e) {\n  if (isModelNotAllowedBody(e.body)) {\n    console.warn(`Model '${e.body.error.offendingLabel}' rejected; allowed:`, e.body.error.allowed);\n    return pickAllowedModel(e.body.error.allowed);\n  }\n  throw e;\n}","preventionTips":["Keep the client's model picker driven by the same allowlist the server enforces.","Surface the 422 error.allowed list to users instead of a generic failure message.","Re-sync actions' model fields after any model-policy change."],"tags":["http-422","model-policy","agent-builder","validation"],"backgroundTag":"model-not-allowed-by-policy","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}