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.

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)

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 parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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