mastra-ai/mastra · error

Factory agent binding is unavailable, revoked, or no longer

Error message

Factory agent binding is unavailable, revoked, or no longer matches this session.

What it means

After re-resolving the session address, the tool looks up the active run binding for it and requires that the binding still exists and still points at the same work item observed when the tools were created. The binding is revoked when a run finishes/rotates roles, and re-pointing a session at a different item is treated as a hijack attempt, so a stale or mismatched tool call is rejected. Comment in source: authority is the work item this session is bound to, not the individual binding row.

Source

Thrown at mastracode/factory/src/rules/tools.ts:83

        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,
          factoryProjectId: binding.factoryProjectId,
          workItemId: binding.workItemId,
          board: boardForSource(item.externalSource?.type),
          stage,
          expectedRevision,
          actor: { type: 'agent', bindingId: binding.id, role: binding.role },
          ingress: { type: 'agent', identity: `${binding.id}:${toolCallId}` },
          cause: rationale,
          ...(triageType ? { triageType } : {}),
        });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Do not reuse transition tools from a previous role/run: rebuild tools for the current role and let the new run call them fresh.
  2. Re-start the run so a new binding is created before calling the tool; verify with findActiveRunBinding that it is active and matches the intended workItemId.
  3. Check for concurrent transitions or double-kickoffs on the same session that rotate/revoke the binding mid-run; serialize runs per session.
  4. Inspect the run binding table for the session address to confirm the current workItemId and role before debugging the agent.
Defensive patterns

Strategy: validation

Validate before calling

const binding = await storage.findActiveRunBinding(address);
const stillValid = !!binding && binding.workItemId === availableBinding.workItemId;
if (!stillValid) throw new Error('Binding rotated or revoked; rebuild tools and restart the run before transitioning.');

Type guard

function isCurrentBinding(
  b: { workItemId: string } | null | undefined,
  expectedWorkItemId: string,
): b is { workItemId: string } {
  return !!b && b.workItemId === expectedWorkItemId;
}

Try / catch

try {
  await transitionTool.execute(input, execution);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Factory agent binding is unavailable')) {
    // binding rotated: end this run's attempts, wait for the new role turn
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the transition tool when `findActiveRunBinding(currentAddress)` returns nothing (binding expired, completed, or revoked), or returns a binding whose `workItemId` differs from `availableBinding.workItemId` captured at tool-creation time (e.g. the session was rotated to the next role or rebound to a different item).

Common situations: An agent retrying a queued/stale tool call after its run's turn ended and the binding rotated to the next role; a long-running agent whose session was rebound to a different work item mid-flight; duplicated/cached tools from a previous role kept live across a rotation; concurrent runs clobbering the binding.

Related errors


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