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

  1. Invoke the tool only through a real agent run so the framework supplies `execution.agent.toolCallId`.
  2. Ensure the session's factory request-context/tags (factoryProjectId, factoryOrgId, session address) are seeded before the run — see `session.state.set` in start-coordinator.
  3. Pass the correct RequestContext (same one used to create the tools) into the agent execution so `resolveFactorySessionAddress` resolves.
  4. 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

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

Related errors


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