{"record":{"id":"dd034d566dcc62da","repo":"Hmbown/CodeWhale","slug":"runtime-import-contains-multiple-threads-export-one-thread-dd034d","errorCode":null,"errorMessage":"Runtime import contains multiple threads. Export one thread before importing.","messagePattern":"Runtime import contains multiple threads\\. Export one thread before importing\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/codewhale.ts","lineNumber":392,"sourceCode":"  }\n  append(records: unknown[]): void {\n    if (!records.length) return;\n    const { events, open, requests } = this;\n    const threadId = this.threadId ?? str(obj(records[0]).thread_id) ?? this.filename;\n    this.threadId = threadId;\n    let { origin, model, skippedDeltas } = this;\n    let threadName = this.threadName ?? threadId;\n    const stamp = (rec: Obj): number => {\n      const t = parseTime(rec.timestamp);\n      if (t === undefined) throw new Error(`Runtime event seq ${rec.seq} is missing a usable timestamp.`);\n      if (origin === undefined) origin = t;\n      return t - origin;\n    };\n    for (const raw of records) {\n      if (!isCodewhaleRuntimeRecord(raw)) throw new Error('Runtime import cancelled: a line is not a Codewhale runtime event record. No rows were skipped.');\n      this.recordCount++;\n      const rec = obj(raw);\n      if (rec.thread_id !== threadId) throw new Error('Runtime import contains multiple threads. Export one thread before importing.');\n      const eventName = rec.event as string;\n      if (eventName === 'item.delta') { skippedDeltas++; continue; }\n      const payload = obj(rec.payload);\n      const item = obj(payload.item);\n      const turn = obj(payload.turn);\n      const thread = obj(payload.thread);\n      const relative = stamp(rec);\n      const turnId = str(rec.turn_id) ?? str(payload.turn_id);\n      const itemId = str(rec.item_id) ?? str(item.id);\n      const agentId = 'parent';\n      if (str(thread.model)) model = str(thread.model);\n      if (str(turn.model)) model = str(turn.model) ?? model;\n\n      if (eventName === 'thread.started') {\n        model = str(thread.model) ?? model;\n        threadName = str(thread.id) ?? threadId;\n        this.push({\n          schemaVersion: 1, id: `thread:${threadId}`, traceId: threadId,","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/codewhale.ts#L374-L410","documentation":"Codewhale runtime event files (NDJSON records) are expected to contain events for exactly one conversation thread. During import, each record's thread_id is compared against the thread established by the first record, and any mismatch aborts the import. The library refuses a partial or misleading import rather than silently merging threads.","triggerScenarios":"Calling fromCodewhaleRuntime (or CodewhaleRuntimeTrace.append) with a records array or file that concatenates events from two or more thread_ids; typically a hand-merged export or a log aggregator that combined multiple sessions into one file.","commonSituations":"Concatenating several exported .jsonl session files with cat; a log shipper writing multiple threads to one file; exporting a whole session store instead of a single thread; a tool that rotates files mid-thread so imports accidentally span files.","solutions":["Export exactly one thread from the runtime and re-import that single file.","Split the input by thread_id (e.g. jq -s or a line-by-line filter grouping on .thread_id) and import each group separately.","Find the offending line(s) whose thread_id differs from the first record and remove or route them to their own file.","Check whether upstream logging or file rotation is merging thread streams and fix the exporter."],"exampleFix":"// before\ncat thread-a.jsonl thread-b.jsonl > combined.jsonl\nimportTrace(fromCodewhaleRuntime(readJsonl('combined.jsonl')))\n// after\nimportTrace(fromCodewhaleRuntime(readJsonl('thread-a.jsonl'), 'thread-a'))\nimportTrace(fromCodewhaleRuntime(readJsonl('thread-b.jsonl'), 'thread-b'))","handlingStrategy":"validation","validationCode":"function isSingleThread(records) {\n  const ids = new Set(records.filter(r => r && typeof r === 'object').map(r => r.thread_id));\n  return ids.size <= 1;\n}\nif (!isSingleThread(records)) throw new Error('input spans multiple thread_id values; split before import');","typeGuard":"const isRuntimeRecord = (r) => !!r && typeof r === 'object' && typeof (r).thread_id === 'string' && typeof (r).event === 'string';","tryCatchPattern":"try {\n  const trace = fromCodewhaleRuntime(records, filename);\n} catch (e) {\n  if (e.message.includes('multiple threads')) {\n    const byThread = new Map();\n    for (const r of records) byThread.set(r.thread_id, [...(byThread.get(r.thread_id) ?? []), r]);\n    for (const [tid, recs] of byThread) importTrace(fromCodewhaleRuntime(recs, tid));\n  } else throw e;\n}","preventionTips":["Export one thread per file from the runtime; never concatenate session exports.","Group raw log lines by thread_id before handing them to the importer.","Validate the first record's thread_id against all others with a cheap pre-scan in your ingestion pipeline.","Keep event-journal files separate from aggregated log sinks."],"tags":["import","validation","codewhale","runtime-events"],"backgroundTag":"incompatible-source-type","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}