mastra-ai/mastra · error

No Factory selected

Error message

No Factory selected

What it means

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.

Solutions

  1. Ensure the component using the hook is rendered under the Factory project route so useParams supplies factoryProjectId.
  2. Gate rendering: only mount the mutation UI when factoryProjectId is truthy (e.g. if (!factoryProjectId) return <FactoryPicker/>).
  3. Pass the hook a guaranteed id: const projectId = factoryProjectId ?? localStorageFactoryProjectId before calling the mutation.
  4. Surface a 'select a Factory' prompt instead of showing mutation buttons when the id is missing.

Example fix

// before
const upsert = useUpsertWorkItemMutation(factoryProjectId);
await upsert.mutateAsync(input); // throws 'No Factory selected' if undefined

// after
if (!factoryProjectId) {
  showFactoryPicker();
  return;
}
const upsert = useUpsertWorkItemMutation(factoryProjectId);
await upsert.mutateAsync(input);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof factoryProjectId !== 'string' || factoryProjectId.length === 0) {
  // render factory picker / abort before calling the mutation
}

Type guard

function hasFactoryProjectId(id: string | undefined | null): id is string {
  return typeof id === 'string' && id.length > 0;
}

Try / catch

try {
  await upsert.mutateAsync(input);
} catch (e) {
  if (e instanceof Error && e.message === 'No Factory selected') {
    openFactoryPicker();
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at mastracode/factory-ui/src/hooks/useWorkItems.ts:31

import { useApiConfig } from '../api/config';
import { queryKeys } from '../api/keys';
import {
  createWorkItem,
  deleteWorkItem,
  listWorkItems,
  transitionWorkItem,
  updateWorkItem,
} from '../ui/domains/factory/services/workItems';
import type {
  BoardSnapshot,
  CreateWorkItemInput,
  FactoryBoard,
  UpdateWorkItemInput,
  WorkItem,
} from '../ui/domains/factory/services/workItems';

function requireFactoryProjectId(factoryProjectId: string | undefined): string {
  if (!factoryProjectId) throw new Error('No Factory selected');
  return factoryProjectId;
}

/** Rewrite the cached board's cards, keeping the run activity read alongside them. */
function patchCards(queryClient: QueryClient, listKey: QueryKey, patch: (cards: WorkItem[]) => WorkItem[]) {
  // Returning undefined skips the write: never seed a partial board before the list query loads.
  queryClient.setQueryData<BoardSnapshot>(listKey, board =>
    board ? { runningSessionIds: board.runningSessionIds, workItems: patch(board.workItems) } : undefined,
  );
}

/** Drop every card ref to a deleted session so the board stops advertising it before the next poll. */
export function stripCachedSessionRefs(queryClient: QueryClient, factoryProjectId: string, sessionId: string) {
  // an in-flight list fetch still carries the stale refs and would clobber the edit below
  void queryClient.cancelQueries({ queryKey: queryKeys.workItems(factoryProjectId) });
  patchCards(queryClient, queryKeys.workItems(factoryProjectId), cards =>
    cards.map(card => {
      const kept = Object.entries(card.sessions).filter(([, ref]) => ref.sessionId !== sessionId);

View on GitHub (pinned to 75dd419e61)