mastra-ai/mastra · error
Factory binding ${binding.id} has no authenticated session o
Error message
Factory binding ${binding.id} has no authenticated session owner. What it means
After finding an active binding, the dispatcher fetches the work item and reads the session's startedBy user for the binding's role. Wake runs must execute under the authenticated user who started the session (credentials are primed and a RequestContext user is set), so if startedBy is missing the library cannot attribute or authorize the run and throws immediately. This is a plain Error, recorded on the pending start and retried with backoff.
Source
Thrown at mastracode/factory/src/rules/dispatcher.ts:928
async leaseExpiresAt =>
this.#storage.renewPendingStartLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt),
async () => {
if (record.message === null) return;
const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId);
const binding = bindings.find(
candidate => candidate.id === record.bindingId && candidate.status === 'active',
);
if (!binding) {
throw new FactoryDispatchError(
'session_unavailable',
'Prepared Factory binding is unavailable or revoked.',
);
}
// Wake runs build the Factory workspace, which requires the
// authenticated session owner on the request context.
const item = await this.#storage.get({ orgId: record.orgId, id: binding.workItemId });
const startedBy = item?.sessions[binding.role]?.startedBy;
if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);
await this.#primeCredentials?.({ orgId: record.orgId, userId: startedBy });
const requestContext = new RequestContext();
requestContext.set('user', { workosId: startedBy, organizationId: record.orgId });
const session = await this.#requireSession(binding);
let resolveAgentEnd!: () => void;
let agentEnd!: Promise<void>;
// The run's own verdict, not the delivery's: a kickoff delivered
// into a run that is already terminating is consumed without
// execution, and completing the pending start on the delivery ack
// alone strands the card with a success ledger entry.
let endReason: 'complete' | 'aborted' | 'error' | 'suspended' | undefined;
const armAgentEnd = () => {
endReason = undefined;
agentEnd = new Promise<void>(resolve => {
resolveAgentEnd = resolve;
});
};
armAgentEnd();View on GitHub (pinned to 75dd419e61)
Solutions
- Ensure the session for binding.role has startedBy populated — re-authenticate/record the starting user on the work item session.
- Check that binding.role matches an actual key in item.sessions (e.g. 'implement' vs 'review') and fix the binding's role if it was renamed.
- Backfill startedBy for imported/migrated work item rows before re-enabling dispatch.
- If the item is missing entirely (get returned undefined), re-create the work item for binding.workItemId.
Example fix
// before: session rows written without an owner
await storage.put({ id: itemId, sessions: { implement: { /* no startedBy */ } } });
// after: always record the authenticated starter
await storage.put({ id: itemId, sessions: { implement: { startedBy: user.workosId } } }); Defensive patterns
Strategy: validation
Validate before calling
const item = await storage.get({ orgId, id: binding.workItemId });
const startedBy = item?.sessions?.[binding.role]?.startedBy;
if (!startedBy) throw new Error(`work item ${binding.workItemId} lacks startedBy for role ${binding.role}`); Type guard
function hasSessionOwner(binding, item) {
return typeof item?.sessions?.[binding.role]?.startedBy === 'string' &&
item.sessions[binding.role].startedBy.length > 0;
} Try / catch
try {
await dispatcher.dispatch(record);
} catch (e) {
if (String(e?.message).includes('has no authenticated session owner')) {
await backfillSessionOwner(record); // re-authenticate and persist startedBy
} else throw e;
} Prevention
- Always record startedBy when creating work item sessions — never write session maps without an owner
- Keep binding.role keys in sync with session map keys when renaming roles
- Backfill startedBy on imported or schema-migrated rows
- Validate work items in tests with the same shape used in production
When it happens
Trigger: The work item row exists but item.sessions[binding.role] is undefined, or that session exists without a startedBy field — typically sessions created outside the authenticated flow, partially-migrated rows, or a role mismatch between the binding and stored sessions.
Common situations: Sessions seeded by scripts/imports that omit startedBy; a binding whose role was renamed or re-pointed after the session map was written; older schema rows predating the startedBy field; tests that stub work items without session metadata.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- GitHub subscriptions require an authenticated repository ses
- Factory decision is not linked to a work item.
- session_unavailable
- Factory review phase requires a selected session model.
- Factory transitions require an authenticated bound agent too
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a5ef10593398a49b.
Report an issue: GitHub.