mastra-ai/mastra · error

Factory work item not found.

Error message

Factory work item not found.

What it means

#requireItem looks up the work item by record.workItemId in storage; if storage.get returns nothing, a plain Error 'Factory work item not found.' is thrown. The decision points at an item id that no longer exists, so dispatch cannot proceed.

Solutions

  1. Confirm the item id and orgId: fetch it directly via storage.get to distinguish deletion from scoping mistakes.
  2. Delete or archive the orphaned decision record so the dispatcher stops retrying it.
  3. Check cleanup/retention jobs for aggressive purging of items referenced by pending decisions.
  4. Re-create the work item (re-materialize from the external source) and re-link the decision.

Example fix

// before: blind retry loop
setInterval(() => dispatchAll(), 60_000);
// after
for (const rec of await listDeferred()) {
  if (!(await storage.get({ orgId: rec.orgId, id: rec.workItemId }))) await storage.deleteDecision(rec.id);
  else await dispatch(rec);
}
Defensive patterns

Strategy: validation

Validate before calling

const item = await storage.get({ orgId: record.orgId, id: record.workItemId });
if (!item) { await purgeDecision(record.id); return; }

Type guard

function itemExists(i: unknown): i is FactoryWorkItem { return typeof (i as any)?.id === 'string'; }

Try / catch

try {
  await dispatcher.dispatch(record);
} catch (e) {
  if (e.message === 'Factory work item not found.') await storage.deleteDecision(record.id);
  else throw e;
}

Prevention

When it happens

Trigger: Dispatching a deferred decision whose workItemId references a deleted work item — e.g. the intake rejection rollback in the same dispatcher deleted the item, external reconciliation removed it, or manual storage cleanup purged it.

Common situations: Cascading deletion (item deleted after a rejected intake), retention/cleanup jobs removing items still referenced by deferred decisions, wrong orgId scoping in manual queries, storage restored from an older backup.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at mastracode/factory/src/rules/dispatcher.ts:810

    const moved = await this.#transitionService.transition({
      orgId: record.orgId,
      factoryProjectId: record.factoryProjectId,
      workItemId: result.item.id,
      board,
      stage: decision.stage,
      expectedRevision,
      actor: { type: 'system', id: 'factory-rule-dispatcher' },
      ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },
      cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',
      causalChain,
    });
    if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);
  }

  async #requireItem(record: FactoryDeferredDecisionRecord) {
    if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');
    const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });
    if (!item) throw new Error('Factory work item not found.');
    return item;
  }

  async #findBinding(
    record: FactoryDeferredDecisionRecord,
    role?: string,
  ): Promise<FactoryRunBindingRecord | undefined> {
    if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');
    const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);
    return bindings
      .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))
      .sort((left, right) => {
        if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;
        if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;
        return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);
      })[0];
  }

View on GitHub (pinned to 75dd419e61)