{"record":{"id":"aa05f6fa719ec6d8","repo":"mastra-ai/mastra","slug":"result-reason","errorCode":null,"errorMessage":"${result.reason}","messagePattern":"\\$\\{result\\.reason\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/factory/hooks/useBoardComposer.ts","lineNumber":47,"sourceCode":"  /**\n   * Submits are re-entrant: a rejected transition keeps the composer open, so a\n   * retry must update the card it already created instead of filing a duplicate.\n   */\n  const submit = async (forStage: BoardStageId, title: string) => {\n    const pendingItem = pendingItemRef.current?.stage === forStage ? pendingItemRef.current : undefined;\n    let item: WorkItem;\n    if (pendingItem === undefined) {\n      item = await create.mutateAsync({ source: 'manual', sourceKey: null, title, stages: ['intake'] });\n      pendingItemRef.current = { stage: forStage, title, item };\n    } else if (pendingItem.title !== title) {\n      item = await update.mutateAsync({ id: pendingItem.item.id, patch: { title } });\n      pendingItemRef.current = { stage: forStage, title, item };\n    } else {\n      item = pendingItem.item;\n    }\n    if (forStage !== 'intake') {\n      const result = await transition.mutateAsync({ item, board: 'work', stage: forStage, cause: 'manual_creation' });\n      if (result.status === 'rejected') throw new Error(result.reason);\n    }\n    pendingItemRef.current = undefined;\n  };\n\n  return {\n    stage,\n    open: setStage,\n    close: (closing: BoardStageId) => {\n      if (pendingItemRef.current?.stage === closing) pendingItemRef.current = undefined;\n      closedStageRef.current = closing;\n      setStage(current => (current === closing ? undefined : current));\n    },\n    registerTrigger: (forStage: BoardStageId) => (element: HTMLButtonElement | null) => {\n      if (element) triggerRefs.current.set(forStage, element);\n      else triggerRefs.current.delete(forStage);\n    },\n    submit,\n  };","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/factory/hooks/useBoardComposer.ts#L29-L65","documentation":"In useBoardComposer.submit, a manual creation flow first transitions the item on the work board via transition.mutateAsync; if the transition result status is 'rejected', the server-provided reason string is thrown verbatim as an Error. The message content is therefore whatever the board-transition service rejected with.","triggerScenarios":"Submitting a composer item for a non-intake stage while the server rejects the board transition (invalid stage for the item, item locked/already moved, permission denied, or conflicting concurrent transition) — result.status === 'rejected' with a reason.","commonSituations":"Two tabs moving the same card concurrently; target stage not allowed by board rules for that item type; user lacks write permission on the board; stale item state after another actor changed it.","solutions":["Read the thrown message — it is the server's rejection reason — and address that specific rule/permission issue.","Refresh the board/item state before retrying (the local item may be stale).","Verify the target stage is valid for the item type and the user has transition permission.","Prevent concurrent edits (single-tab workflows or optimistic-locking on the server)."],"exampleFix":"// before\nif (result.status === 'rejected') throw new Error(result.reason);\n// after\nif (result.status === 'rejected') {\n  await queryClient.invalidateQueries(['board', 'work']);\n  throw new Error(result.reason);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"type TransitionResult = { status: 'ok' } | { status: 'rejected'; reason: string };\nfunction isRejected(r: TransitionResult): r is { status: 'rejected'; reason: string } {\n  return r.status === 'rejected';\n}","tryCatchPattern":"try {\n  await submit({ forStage, title, item });\n} catch (e) {\n  showToast(e instanceof Error ? e.message : 'Board transition rejected');\n  await queryClient.invalidateQueries({ queryKey: ['board'] }); // resync stale state\n}","preventionTips":["Refresh board state before retrying after a rejection.","Check permissions and stage rules for the item type before submit.","Avoid concurrent edits of the same item across tabs."],"tags":["board-transition","server-rejection","react-query","state-conflict"],"backgroundTag":"operation-rejected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}