{"record":{"id":"ad1ed74d38de9d04","repo":"shadcn-ui/ui","slug":"only-continuation-turns-materialize-from-a-transcr","errorCode":null,"errorMessage":"Only continuation turns materialize from a transcript.","messagePattern":"Only continuation turns materialize from a transcript\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/helpers/src/core/chat.ts","lineNumber":496,"sourceCode":"          : undefined,\n      ...(call.approval\n        ? {\n            approved: approved === true,\n            denied: approved === false,\n          }\n        : {}),\n    } as PendingToolCall<TOOLS>\n  }\n\n  function materializeDeferredTurn(\n    turnIndex: number,\n    messages: MESSAGE[]\n  ): ResolvedTurn {\n    const turn = turns[turnIndex]\n    const resolve = turn.resolve\n\n    if (!resolve) {\n      throw new Error(\"Only continuation turns materialize from a transcript.\")\n    }\n\n    const events = [...turn.events]\n\n    // The client merges a continuation into the prior assistant message as a\n    // new step. The step boundary keeps the resolved tool call out of the\n    // latest step so automatic sending does not retrigger on it.\n    events.push({ kind: \"step-start\" })\n\n    const pending = resolvePendingToolCalls(turnIndex, messages)\n\n    if (!pending.length) {\n      devWarn(\n        \"A continuation turn resolved without a pending tool call. Its toolCall context is empty.\"\n      )\n    }\n\n    for (const call of pending) {","sourceCodeStart":478,"sourceCodeEnd":514,"githubUrl":"https://github.com/shadcn-ui/ui/blob/efac5987074af84ece57c367c6dd83387b967022/packages/helpers/src/core/chat.ts#L478-L514","documentation":"materializeDeferredTurn() rebuilds a deferred (continuation) turn's events from a live transcript; it requires turns[turnIndex].resolve to be set. Calling it on a regular, already-resolved turn is a logic error because that turn has no deferred resolution to run. This is an internal invariant guard, not a user-input error.","triggerScenarios":"Internal mis-indexing where findNextAssistantTurnIndex points at a non-deferred turn; calling materializeDeferredTurn directly with a wrong turnIndex; a transcript whose continuation marker was cleared.","commonSituations":"Bugs in turn-index resolution; custom adapters that invoke materializeDeferredTurn; corrupt transcript state from partial persistence.","solutions":["Guard callers so materializeDeferredTurn only runs when turns[turnIndex].resolve is defined.","Re-check findNextAssistantTurnIndex logic if this fires from the built-in transport.","Treat materializeDeferredTurn as internal API; do not call it from adapter or app code."],"exampleFix":"// before\nmaterializeDeferredTurn(turnIndex, messages)\n\n// after\nif (turns[turnIndex]?.resolve) {\n  materializeDeferredTurn(turnIndex, messages)\n}","handlingStrategy":"validation","validationCode":"const turn = turns[turnIndex]\nif (!turn?.resolve) {\n  // not a continuation turn; nothing to materialize\n  return\n}\nmaterializeDeferredTurn(turnIndex, messages)","typeGuard":"const isDeferredTurn = (t: unknown): t is { resolve: (...a: unknown[]) => unknown } =>\n  Boolean(t && typeof (t as { resolve?: unknown }).resolve === \"function\")","tryCatchPattern":null,"preventionTips":["Treat materializeDeferredTurn as internal; do not call it from app or adapter code.","Keep turn-index computation centralized so resolve aligns with the selected index."],"tags":["chat","transcript","internal","continuation"],"backgroundTag":null,"analyzedSha":"efac5987074af84ece57c367c6dd83387b967022","analyzedAt":"2026-08-12T05:00:50.218Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}