mastra-ai/mastra · error
Factory transitions require an authenticated bound agent too
Error message
Factory transitions require an authenticated bound agent tool call.
What it means
The `factory_transition_work_item` tool only executes for a genuine, authenticated bound-agent tool call. During execution it re-resolves the session address from request context and requires both a resolved session address and an `execution.agent.toolCallId`. If either is missing, the call is not a recognized agent tool invocation and cannot be attributed to a binding, so it is refused. This is an anti-hijack/authority gate: transitions must be attributable to a specific agent turn on a bound session.
Source
Thrown at mastracode/factory/src/rules/tools.ts:73
return {
factory_transition_work_item: createTool({
id: 'factory_transition_work_item',
description: isTriage
? 'Report the triage classification and request a governed stage transition for the Factory work item exactly bound to this thread. Only bugs may request Planning autonomously; closure outcomes may request a terminal stage. Feature requests and other non-bug classifications that remain open must stay in their current Intake or Triage stage for maintainer approval.'
: 'Request a governed stage transition for the Factory work item exactly bound to this thread. Use the current revision from the factory-phase signal and explain why the transition is appropriate.',
inputSchema: isTriage ? triageTransitionInputSchema : transitionInputSchema,
requireApproval: true,
execute: async ({ stage, expectedRevision, rationale, ...input }, execution) => {
const currentResolution = await resolveFactorySessionAddress({
requestContext: execution.requestContext,
storage: options.storage,
sessions: options.sessions,
});
const currentAddress = currentResolution?.address ?? null;
const toolCallId = execution.agent?.toolCallId;
if (!currentAddress || !toolCallId) {
throw new Error('Factory transitions require an authenticated bound agent tool call.');
}
const binding = await options.storage.findActiveRunBinding(currentAddress);
// Authority is the work item this session is bound to, not the individual
// binding row. Handing the next role its turn in an existing session
// rotates the binding, and tools built for the previous role stay live
// across that rotation; keying on row identity would strand the run that
// the rotation exists to start. Re-pointing a session at a different item
// is the hijack this guards against.
if (!binding || binding.workItemId !== availableBinding.workItemId) {
throw new Error('Factory agent binding is unavailable, revoked, or no longer matches this session.');
}
const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });
if (!item) throw new Error('Bound Factory work item not found.');
const triageType =
'triageType' in input && isFactoryTriageType(input.triageType) ? input.triageType : undefined;
const result = await options.transitionService.transition({
orgId: binding.orgId,View on GitHub (pinned to 75dd419e61)
Solutions
- Invoke the tool only through a real agent run so the framework supplies `execution.agent.toolCallId`.
- Ensure the session's factory request-context/tags (factoryProjectId, factoryOrgId, session address) are seeded before the run — see `session.state.set` in start-coordinator.
- Pass the correct RequestContext (same one used to create the tools) into the agent execution so `resolveFactorySessionAddress` resolves.
- Check @mastra/core version: if `execution.agent.toolCallId` moved, update the factory tools package to a compatible core version.
Example fix
// before
tool.execute({ stage: 'building', expectedRevision: 3, rationale: 'done' });
// after
const run = await agent.start(prompt, { requestContext, resourceid: boundSessionId });
// let the agent call the tool so execution.agent.toolCallId is present Defensive patterns
Strategy: try-catch
Validate before calling
const res = await resolveFactorySessionAddress({ requestContext, storage, sessions });
if (!res?.address) throw new Error('No factory session address; transition tool will refuse.'); Type guard
function isBoundAgentExecution(
ex: unknown,
): ex is { requestContext: RequestContext; agent: { toolCallId: string } } {
return !!ex && typeof ex === 'object' && 'agent' in ex &&
!!(ex as any).agent?.toolCallId && !!(ex as any).requestContext;
} Try / catch
try {
await transitionTool.execute(input, execution);
} catch (e) {
if (e instanceof Error && e.message.includes('authenticated bound agent tool call')) {
// surface to the model as a non-retryable authority error
} else throw e;
} Prevention
- Never call factory transition tools outside an agent run; drive them through the agent loop only.
- Seed session state (factoryProjectId, factoryOrgId, session address) server-side before the run, as start-coordinator does.
- After upgrading @mastra/core, verify execution.agent.toolCallId is still populated in tool execute context.
- Write a test asserting the tool rejects executions lacking toolCallId.
When it happens
Trigger: The tool's `execute` runs without `execution.agent.toolCallId` (e.g. invoked programmatically/outside an agent run), or `resolveFactorySessionAddress` returns null/undefined for `execution.requestContext` (no session address in request context), so `currentAddress` or `toolCallId` is null.
Common situations: Calling the tool directly from tests or scripts instead of through an agent run; running the agent without the factory request-context (factoryProjectId/session tags) populated; a framework upgrade changing the execution context shape so `execution.agent` is no longer populated.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Factory binding ${binding.id} has no authenticated session o
- Factory agent binding is unavailable, revoked, or no longer
- ${errorData.message || 'Invalid email or password'}
- Invalid email or password
- ${errorData.message || 'Failed to create account'}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/22f9166d5c778b02.
Report an issue: GitHub.