{"record":{"id":"990cdc4a781252bb","repo":"mastra-ai/mastra","slug":"no-factory-selected","errorCode":null,"errorMessage":"No Factory selected","messagePattern":"No Factory selected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/hooks/useWorkItems.ts","lineNumber":31,"sourceCode":"import { useApiConfig } from '../api/config';\nimport { queryKeys } from '../api/keys';\nimport {\n  createWorkItem,\n  deleteWorkItem,\n  listWorkItems,\n  transitionWorkItem,\n  updateWorkItem,\n} from '../ui/domains/factory/services/workItems';\nimport type {\n  BoardSnapshot,\n  CreateWorkItemInput,\n  FactoryBoard,\n  UpdateWorkItemInput,\n  WorkItem,\n} from '../ui/domains/factory/services/workItems';\n\nfunction requireFactoryProjectId(factoryProjectId: string | undefined): string {\n  if (!factoryProjectId) throw new Error('No Factory selected');\n  return factoryProjectId;\n}\n\n/** Rewrite the cached board's cards, keeping the run activity read alongside them. */\nfunction patchCards(queryClient: QueryClient, listKey: QueryKey, patch: (cards: WorkItem[]) => WorkItem[]) {\n  // Returning undefined skips the write: never seed a partial board before the list query loads.\n  queryClient.setQueryData<BoardSnapshot>(listKey, board =>\n    board ? { runningSessionIds: board.runningSessionIds, workItems: patch(board.workItems) } : undefined,\n  );\n}\n\n/** Drop every card ref to a deleted session so the board stops advertising it before the next poll. */\nexport function stripCachedSessionRefs(queryClient: QueryClient, factoryProjectId: string, sessionId: string) {\n  // an in-flight list fetch still carries the stale refs and would clobber the edit below\n  void queryClient.cancelQueries({ queryKey: queryKeys.workItems(factoryProjectId) });\n  patchCards(queryClient, queryKeys.workItems(factoryProjectId), cards =>\n    cards.map(card => {\n      const kept = Object.entries(card.sessions).filter(([, ref]) => ref.sessionId !== sessionId);","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/hooks/useWorkItems.ts#L13-L49","documentation":"requireFactoryProjectId is a guard helper in the Factory UI work-items hooks. It throws 'No Factory selected' whenever the factoryProjectId argument is undefined, refusing to issue work-item API calls (create/update/transition) against an unspecified Factory board. The guard exists so mutations fail fast client-side instead of hitting the server with an ambiguous or malformed URL.","triggerScenarios":"Calling useUpsertWorkItemMutation (or any hook that internally calls requireFactoryProjectId) with factoryProjectId === undefined, then invoking the returned mutation. Typically happens when the route param ':factoryProjectId' is missing, the query is still loading, or the hook was rendered outside the Factory project route.","commonSituations":"Deep-linking to a work-item page without the project id in the URL; rendering the work-items component before the factory/project selection resolves; copying a hook into a context where no Factory is selected (e.g. a dashboard outside /factories/:id); a refactor that made the id optional in the hook signature.","solutions":["Ensure the component using the hook is rendered under the Factory project route so useParams supplies factoryProjectId.","Gate rendering: only mount the mutation UI when factoryProjectId is truthy (e.g. if (!factoryProjectId) return <FactoryPicker/>).","Pass the hook a guaranteed id: const projectId = factoryProjectId ?? localStorageFactoryProjectId before calling the mutation.","Surface a 'select a Factory' prompt instead of showing mutation buttons when the id is missing."],"exampleFix":"// before\nconst upsert = useUpsertWorkItemMutation(factoryProjectId);\nawait upsert.mutateAsync(input); // throws 'No Factory selected' if undefined\n\n// after\nif (!factoryProjectId) {\n  showFactoryPicker();\n  return;\n}\nconst upsert = useUpsertWorkItemMutation(factoryProjectId);\nawait upsert.mutateAsync(input);","handlingStrategy":"validation","validationCode":"if (typeof factoryProjectId !== 'string' || factoryProjectId.length === 0) {\n  // render factory picker / abort before calling the mutation\n}","typeGuard":"function hasFactoryProjectId(id: string | undefined | null): id is string {\n  return typeof id === 'string' && id.length > 0;\n}","tryCatchPattern":"try {\n  await upsert.mutateAsync(input);\n} catch (e) {\n  if (e instanceof Error && e.message === 'No Factory selected') {\n    openFactoryPicker();\n  } else throw e;\n}","preventionTips":["Only render factory-scoped mutation UI inside the route that supplies factoryProjectId.","Disable submit actions until required ids are truthy.","Centralize id extraction from route params in one guarded helper."],"tags":["react","react-query","missing-parameter","factory-ui"],"backgroundTag":"missing-required-parameter","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}