{"record":{"id":"646a361cbdbd5271","repo":"n8n-io/n8n","slug":"no-runs-for-thread-ref-threadid-in-langsmith-pr","errorCode":null,"errorMessage":"No runs for thread ${ref.threadId} in LangSmith project \"${sourceProject}\" — the trace may have aged out (~14-day base retention) or the project name is wrong.","messagePattern":"No runs for thread (.+?) in LangSmith project \"(.+?)\" — the trace may have aged out \\(~14-day base retention\\) or the project name is wrong\\.","errorType":"exception","errorClass":"ThreadNotInWorkspaceError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/harness/langsmith-seed.ts","lineNumber":348,"sourceCode":"\tref: SeedThreadRef,\n\tclient: Client,\n\tsourceProject: string,\n): Promise<ReconstructedSeed> {\n\tconst runs: Run[] = [];\n\t// Fetch only the run_types reconstruction uses — root `chain` turns + `tool` runs.\n\t// Paging every run_type (the llm/nested bulk is usually the majority) multiplied\n\t// /runs/query calls and tripped LangSmith rate limits on long threads. No is_root\n\t// filter: tools are non-root, so it can't be expressed as a single boolean.\n\tfor await (const run of client.listRuns({\n\t\tprojectName: sourceProject,\n\t\tfilter: `and(eq(thread_id, \"${ref.threadId}\"), or(eq(run_type, \"chain\"), eq(run_type, \"tool\")))`,\n\t})) {\n\t\truns.push(run);\n\t}\n\tif (runs.length === 0) {\n\t\t// Recognised by discovery to advance to the next workspace; the message\n\t\t// still reads well if it surfaces directly (explicit-client path).\n\t\tthrow new ThreadNotInWorkspaceError(\n\t\t\t`No runs for thread ${ref.threadId} in LangSmith project \"${sourceProject}\" — the trace may have aged out (~14-day base retention) or the project name is wrong.`,\n\t\t);\n\t}\n\n\t// `?? NaN` keeps the SDK's optional start_time behavior-identical: an absent\n\t// value still yields NaN comparisons, never a valid epoch-0 date.\n\tconst byStartTime = (a: Run, b: Run) =>\n\t\tnew Date(a.start_time ?? NaN).getTime() - new Date(b.start_time ?? NaN).getTime();\n\tconst rootRuns = runs.filter((r) => r.run_type === 'chain' && !r.parent_run_id).sort(byStartTime);\n\t// Real agent tool calls only — the compiled-workflow bookkeeping event is\n\t// excluded BY NAME (it must never become a tool-call block in the rebuilt\n\t// transcript, whatever run_type it was emitted with).\n\tconst toolRuns = runs\n\t\t.filter((r) => r.run_type === 'tool' && r.name !== COMPILED_WORKFLOW_TRACE_RUN_NAME)\n\t\t.sort(byStartTime);\n\t// Workflow reconstruction additionally scans the compiled-workflow events\n\t// (chain-typed; matched by name so legacy tool-typed events still count).\n\tconst workflowScanRuns = runs","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/harness/langsmith-seed.ts#L330-L366","documentation":"ThreadNotInWorkspaceError is thrown by the LangSmith seed harness when a client.listRuns query returns zero runs for the given thread_id in the chosen sourceProject. It signals the discovery loop to advance to the next candidate workspace rather than treating an empty result as a real seed. The query filters for chain/tool run_types and the message notes the two common root causes: trace retention expiry (~14-day base window) or a wrong project name.","triggerScenarios":"Calling buildSeedFromLangsmith with a ref.threadId that has no matching runs in sourceProject; LangSmith retention has aged the trace out; the project name passed does not match the actual LangSmith project that contains the thread; the thread_id value is malformed or stale.","commonSituations":"Replaying an old eval case whose trace was captured >14 days ago; copy-paste of a thread ID from a different LangSmith org/project; discovery iteration hitting an empty project on its way to the correct one; running seed reconstruction against a personal LangSmith project instead of the shared team one.","solutions":["Confirm the trace still exists by running `client.listRuns({ projectName, filter: 'eq(thread_id, \"<id>\")' })` directly and check it returns runs.","Verify the sourceProject string matches the LangSmith project exactly (case-sensitive, no trailing whitespace).","If the trace aged out, re-record the conversation in LangTracer to produce a fresh trace and update the case's threadId reference.","If this is discovery, treat it as expected and let the loop advance to the next workspace (the error is recognised by discovery)."],"exampleFix":"// before\nbuildSeedFromLangsmith(client, { threadId: 'old-thread', sourceProject: 'prod-traces' }, ...)\n\n// after — verify the run exists and use the current project\nconst probe = await client.listRuns({ projectName: 'prod-traces', filter: `eq(thread_id, \"${threadId}\")`, limit: 1 });\nif (!probe.length) throw new Error('thread missing — re-record trace');\nbuildSeedFromLangsmith(client, { threadId, sourceProject: 'prod-traces' }, ...)","handlingStrategy":"try-catch","validationCode":"import type { Client } from 'langsmith';\n\nasync function threadHasRuns(client: Client, project: string, threadId: string): Promise<boolean> {\n  const probe = await client.listRuns({\n    projectName: project,\n    filter: `eq(thread_id, \"${threadId}\")`,\n    limit: 1,\n  });\n  return probe.length > 0;\n}\n\n// before seeding:\nif (!(await threadHasRuns(client, sourceProject, ref.threadId))) {\n  throw new Error(`pre-check: thread ${ref.threadId} has no runs in ${sourceProject}`);\n}","typeGuard":"import { ThreadNotInWorkspaceError } from './langsmith-seed';\n\nfunction isThreadNotInWorkspaceError(e: unknown): e is ThreadNotInWorkspaceError {\n  return e instanceof ThreadNotInWorkspaceError;\n}","tryCatchPattern":"try {\n  await buildSeedFromLangsmith(client, ref, sourceProject, logger);\n} catch (e) {\n  if (e instanceof ThreadNotInWorkspaceError) {\n    // expected during discovery — advance to the next workspace\n    continue;\n  }\n  throw e;\n}","preventionTips":["Cache the trace's last-seen timestamp and skip seeding if it is older than the project's retention window.","Centralise project-name configuration to avoid typos across cases.","For discovery flows, treat ThreadNotInWorkspaceError as a signal, not a fault."],"tags":["langsmith","eval-harness","trace-expiry","configuration"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}