mastra-ai/mastra · error
Factory kickoff was queued onto an ending run and never…
Error message
Factory kickoff was queued onto an ending run and never reached the agent.
What it means
During a kick-off retry against an in-flight run, the dispatcher queued the kickoff message onto the run as it was ending. sendKickoff returns a settled result whose action must be 'wake' to prove the message reached the agent; any other action (or no result) means the kickoff was swallowed by a terminating run and the card would strand. The library throws so the record is retried with a fresh dedupe-safe kickoff key.
Solutions
- Rely on the built-in retry: the throw marks the pending start failed with a retryAt schedule; ensure MAX_ATTEMPTS is not exhausted.
- Verify the inbox/dedupe layer is not swallowing retried kickoffs (each retry uses ':retry:<attempts>' in the key — check the key is unique).
- Check agent/session logs for the run's end event timing; if teardown routinely races delivery, serialize kickoff after confirmed run end.
- Ensure the agent process is stable (no crashes mid-run) — process restarts are the dominant cause per the surrounding code.
Defensive patterns
Strategy: retry
Try / catch
try {
await dispatcher.dispatch(record);
} catch (e) {
if (String(e?.message).includes('never reached the agent')) {
await scheduleRetry(record); // bounded by MAX_ATTEMPTS; kickoff key is attempt-suffixed
} else throw e;
} Prevention
- Keep MAX_ATTEMPTS and retry backoff configured so transient delivery races self-heal
- Don't bypass the generation-scoped dedupeKey mechanism with hand-rolled keys
- Ensure the agent process drains gracefully so runs don't terminate mid-delivery
- Watch for inbox dedupe suppressing retried kickoffs in logs
When it happens
Trigger: sendKickoff resolved with settled.action !== 'wake' — the run terminated between armAgentEnd() and delivery, the inbox rejected the message via dedupe, or the delivery landed after the agent stopped accepting input.
Common situations: Race between run teardown and message delivery under load; duplicate kickoff messages where generation-scoped dedupeKey suppressed a retry delivery; process crash mid-delivery; attempting wake on a run that already emitted its end event.
Related errors
- Factory kickoff is waiting on a run that has not ended.
- Factory session was retired during workspace materialization
- Factory workspace GitHub credential registration is no…
- GitHub token refresh no longer matches the active Factory…
- AbortError
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d7cf7cdf32c9cda2.
Report an issue: GitHub.
Appendix: source
Thrown at mastracode/factory/src/rules/dispatcher.ts:987
);
try {
let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);
if (settled?.action === 'deliver') {
// `deliver` only proves the signal was queued onto a run already
// in flight. If that run ends without draining its queue the
// kickoff is dropped silently. There is no per-notification
// "processed" signal, so wait for the in-flight run to end and
// redeliver into the idle session unconditionally — the
// generation-scoped dedupeKey defeats inbox dedupe and the
// kickoff key keeps a duplicate run bounded, while a dropped
// kickoff strands the card forever.
if (!(await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs))) {
throw new Error('Factory kickoff is waiting on a run that has not ended.');
}
armAgentEnd();
settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);
if (settled?.action !== 'wake') {
throw new Error('Factory kickoff was queued onto an ending run and never reached the agent.');
}
}
const observed = await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs);
if (!observed) {
throw new Error('Factory kickoff run terminal event was not observed before timeout.');
} else if (endReason === 'error') {
throw new Error('Factory kickoff run ended in error.');
} else if (endReason === 'aborted') {
// Retryable for the same reason as skill decisions: the dominant
// cause is the process going away underneath the run, not a
// deliberate stop, and a spurious retry is bounded by
// MAX_ATTEMPTS while a dead card costs a human a manual nudge.
throw new Error('Factory kickoff run was aborted before it finished.');
}
} finally {
unsubscribe();
}
},View on GitHub (pinned to 75dd419e61)