{"record":{"id":"13bfeff9c465142c","repo":"mastra-ai/mastra","slug":"factory-decision-is-not-linked-to-a-work-item","errorCode":null,"errorMessage":"Factory decision is not linked to a work item.","messagePattern":"Factory decision is not linked to a work item\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/rules/dispatcher.ts","lineNumber":808,"sourceCode":"    if (decision.stage === 'intake') return;\n\n    const moved = await this.#transitionService.transition({\n      orgId: record.orgId,\n      factoryProjectId: record.factoryProjectId,\n      workItemId: result.item.id,\n      board,\n      stage: decision.stage,\n      expectedRevision,\n      actor: { type: 'system', id: 'factory-rule-dispatcher' },\n      ingress: { type: 'rule', identity: `decision:${record.idempotencyKey}:${result.item.id}:destination` },\n      cause: materializedByDecision ? 'linked_item_materialized' : 'linked_item_reconciled',\n      causalChain,\n    });\n    if (moved.status === 'rejected') throw new Error(`${moved.code}: ${moved.reason}`);\n  }\n\n  async #requireItem(record: FactoryDeferredDecisionRecord) {\n    if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n    const item = await this.#storage.get({ orgId: record.orgId, id: record.workItemId });\n    if (!item) throw new Error('Factory work item not found.');\n    return item;\n  }\n\n  async #findBinding(\n    record: FactoryDeferredDecisionRecord,\n    role?: string,\n  ): Promise<FactoryRunBindingRecord | undefined> {\n    if (!record.workItemId) throw new Error('Factory decision is not linked to a work item.');\n    const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);\n    return bindings\n      .filter(candidate => candidate.status === 'active' && (role === undefined || candidate.role === role))\n      .sort((left, right) => {\n        if (role === undefined && left.role === 'work' && right.role !== 'work') return -1;\n        if (role === undefined && right.role === 'work' && left.role !== 'work') return 1;\n        return right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id);\n      })[0];","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/rules/dispatcher.ts#L790-L826","documentation":"#requireItem enforces that a deferred decision record carries a workItemId before doing anything item-related. If record.workItemId is falsy, a plain Error 'Factory decision is not linked to a work item.' is thrown. It indicates a data-integrity problem: a decision was deferred/persisted without ever being linked to a created work item.","triggerScenarios":"Calling dispatcher paths that resolve the work item (#requireItem via binding/session preparation) with a FactoryDeferredDecisionRecord whose workItemId is null/undefined — e.g. a record saved before item creation failed, or a manually constructed record.","commonSituations":"A previous dispatch crashed between deferral and item creation, leaving an orphan decision record; importing/restoring storage data that lost the link; tests constructing partial records.","solutions":["Query storage for decisions with null workItemId and delete or re-enqueue them after re-materializing the item.","Fix the upstream step that should create the work item and set workItemId before deferral completes.","If constructing records manually (tests), always set workItemId to a valid stored item id."],"exampleFix":"// before\ndispatch(record); // record.workItemId missing\n// after\nif (!record.workItemId) record = await reMaterializeAndLink(record);\nawait dispatcher.dispatch(record);","handlingStrategy":"validation","validationCode":"function canDispatch(record: FactoryDeferredDecisionRecord): boolean {\n  return typeof record.workItemId === 'string' && record.workItemId.length > 0;\n}","typeGuard":"function isLinkedDecision(r: FactoryDeferredDecisionRecord): r is FactoryDeferredDecisionRecord & { workItemId: string } {\n  return typeof r.workItemId === 'string' && r.workItemId.length > 0;\n}","tryCatchPattern":"try {\n  await dispatcher.dispatch(record);\n} catch (e) {\n  if (e.message === 'Factory decision is not linked to a work item.') await reMaterializeAndLink(record);\n  else throw e;\n}","preventionTips":["Never persist deferred decisions without a workItemId.","Add a startup sweeper for orphan decisions lacking a work item link.","Validate records at storage write time with a schema."],"tags":["data-integrity","factory","dispatch","missing-link"],"backgroundTag":"missing-required-argument","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}