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

  1. Rely on the built-in retry: the throw marks the pending start failed with a retryAt schedule; ensure MAX_ATTEMPTS is not exhausted.
  2. Verify the inbox/dedupe layer is not swallowing retried kickoffs (each retry uses ':retry:<attempts>' in the key — check the key is unique).
  3. Check agent/session logs for the run's end event timing; if teardown routinely races delivery, serialize kickoff after confirmed run end.
  4. 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

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


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)