{"record":{"id":"c621b0e0e93b9c8d","repo":"block/buzz","slug":"choose-a-channel-first","errorCode":null,"errorMessage":"Choose a channel first.","messagePattern":"Choose a channel first\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/features/workflows/ui/WorkflowMessagePicker.tsx","lineNumber":76,"sourceCode":"  onEscape?: () => void;\n  value: string;\n}) {\n  const optionRefs = React.useRef(new Map<string, HTMLButtonElement>());\n  const [query, setQuery] = React.useState(\"\");\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n  const trimmedQuery = query.trim();\n  const deferredQuery = React.useDeferredValue(trimmedQuery);\n  const normalizedQuery = deferredQuery.toLowerCase();\n  const selectedId = normalizeMessageEventId(value);\n  const directId = normalizeMessageEventId(query);\n  const lookupId = directId ?? selectedId;\n\n  const historyQuery = useInfiniteQuery({\n    enabled: Boolean(channelId),\n    initialPageParam: null as ChannelPageCursor | null,\n    queryKey: [\"workflow-message-picker\", channelId],\n    queryFn: async ({ pageParam }) => {\n      if (!channelId) throw new Error(\"Choose a channel first.\");\n      return parseChannelWindowResponse(\n        await getChannelWindowEvents(channelId, pageParam, PAGE_SIZE),\n        channelId,\n        pageParam,\n      );\n    },\n    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,\n    staleTime: 30_000,\n  });\n  const searchQuery = useSearchMessagesQuery(deferredQuery, {\n    channelId: channelId ?? undefined,\n    enabled: Boolean(channelId && normalizedQuery && !directId),\n    limit: 30,\n    minimumQueryLength: 1,\n  });\n  const exactQuery = useQuery({\n    enabled: Boolean(channelId && lookupId),\n    queryKey: [\"workflow-message-picker-exact\", channelId, lookupId],","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/features/workflows/ui/WorkflowMessagePicker.tsx#L58-L94","documentation":"WorkflowMessagePicker's infinite history query only runs when `channelId` is truthy (`enabled: Boolean(channelId)`), but the queryFn re-checks and throws 'Choose a channel first.' as a defensive guard. This means the fetch function was invoked without a channel id — normally impossible while `enabled` gates it, so the throw surfaces a state/timing bug or a direct call rather than an expected condition.","triggerScenarios":"Invoking `historyQuery.refetch()` (or the queryFn) while `channelId` is null/undefined; the picker being rendered and forced to fetch before a channel is selected; code that manually calls the query function outside React Query's enabled gating.","commonSituations":"Opening the workflow message picker before selecting a channel and triggering a refetch; remount race where stale query state re-fires the queryFn; automated tests calling the query function directly; channelId cleared (channel deleted or switched) while a refetch is in flight.","solutions":["Select a channel so `channelId` is set before triggering the history query or refetch.","Gate any manual `refetch()` on `Boolean(channelId)` so it is never called without a channel.","Check why React Query ran the query despite `enabled: Boolean(channelId)` — look for imperative queryClient.fetchQuery calls bypassing the gate.","In tests, provide a channelId fixture or mock the query before rendering the picker."],"exampleFix":"// before\nif (!channelId) throw new Error(\"Choose a channel first.\");\n// after — caller guards before refetch\nconst pickChannel = async () => {\n  if (!channelId) return; // UI already requires a selection\n  await historyQuery.refetch();\n};","handlingStrategy":"validation","validationCode":"if (!channelId) return null; // or render a 'pick a channel' placeholder\nconst historyQuery = useInfiniteQuery({\n  enabled: Boolean(channelId),\n  queryKey: [\"workflow-message-picker\", channelId],\n  queryFn: ({ pageParam }) => getChannelWindowEvents(channelId!, pageParam, PAGE_SIZE),\n});","typeGuard":"const hasChannel = (id: string | null | undefined): id is string =>\n  typeof id === \"string\" && id.length > 0;","tryCatchPattern":"try {\n  await historyQuery.refetch();\n} catch (e) {\n  if (e instanceof Error && e.message === \"Choose a channel first.\") return;\n  throw e;\n}","preventionTips":["Only call refetch() when Boolean(channelId) is true.","Render the picker only after a channel is selected.","Avoid imperative queryClient.fetch* calls that bypass the enabled gate."],"tags":["react-query","validation","ui","async"],"backgroundTag":"missing-required-argument","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}