n8n-io/n8n · error · ThreadNotInWorkspaceError
No runs for thread ${ref.threadId} in LangSmith project "${s
Error message
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. What it means
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.
Source
Thrown at packages/@n8n/instance-ai/evaluations/harness/langsmith-seed.ts:348
ref: SeedThreadRef,
client: Client,
sourceProject: string,
): Promise<ReconstructedSeed> {
const runs: Run[] = [];
// Fetch only the run_types reconstruction uses — root `chain` turns + `tool` runs.
// Paging every run_type (the llm/nested bulk is usually the majority) multiplied
// /runs/query calls and tripped LangSmith rate limits on long threads. No is_root
// filter: tools are non-root, so it can't be expressed as a single boolean.
for await (const run of client.listRuns({
projectName: sourceProject,
filter: `and(eq(thread_id, "${ref.threadId}"), or(eq(run_type, "chain"), eq(run_type, "tool")))`,
})) {
runs.push(run);
}
if (runs.length === 0) {
// Recognised by discovery to advance to the next workspace; the message
// still reads well if it surfaces directly (explicit-client path).
throw new ThreadNotInWorkspaceError(
`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.`,
);
}
// `?? NaN` keeps the SDK's optional start_time behavior-identical: an absent
// value still yields NaN comparisons, never a valid epoch-0 date.
const byStartTime = (a: Run, b: Run) =>
new Date(a.start_time ?? NaN).getTime() - new Date(b.start_time ?? NaN).getTime();
const rootRuns = runs.filter((r) => r.run_type === 'chain' && !r.parent_run_id).sort(byStartTime);
// Real agent tool calls only — the compiled-workflow bookkeeping event is
// excluded BY NAME (it must never become a tool-call block in the rebuilt
// transcript, whatever run_type it was emitted with).
const toolRuns = runs
.filter((r) => r.run_type === 'tool' && r.name !== COMPILED_WORKFLOW_TRACE_RUN_NAME)
.sort(byStartTime);
// Workflow reconstruction additionally scans the compiled-workflow events
// (chain-typed; matched by name so legacy tool-typed events still count).
const workflowScanRuns = runsView on GitHub (pinned to 5ac6606e81)
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).
Example fix
// before
buildSeedFromLangsmith(client, { threadId: 'old-thread', sourceProject: 'prod-traces' }, ...)
// after — verify the run exists and use the current project
const probe = await client.listRuns({ projectName: 'prod-traces', filter: `eq(thread_id, "${threadId}")`, limit: 1 });
if (!probe.length) throw new Error('thread missing — re-record trace');
buildSeedFromLangsmith(client, { threadId, sourceProject: 'prod-traces' }, ...) Defensive patterns
Strategy: try-catch
Validate before calling
import type { Client } from 'langsmith';
async function threadHasRuns(client: Client, project: string, threadId: string): Promise<boolean> {
const probe = await client.listRuns({
projectName: project,
filter: `eq(thread_id, "${threadId}")`,
limit: 1,
});
return probe.length > 0;
}
// before seeding:
if (!(await threadHasRuns(client, sourceProject, ref.threadId))) {
throw new Error(`pre-check: thread ${ref.threadId} has no runs in ${sourceProject}`);
} Type guard
import { ThreadNotInWorkspaceError } from './langsmith-seed';
function isThreadNotInWorkspaceError(e: unknown): e is ThreadNotInWorkspaceError {
return e instanceof ThreadNotInWorkspaceError;
} Try / catch
try {
await buildSeedFromLangsmith(client, ref, sourceProject, logger);
} catch (e) {
if (e instanceof ThreadNotInWorkspaceError) {
// expected during discovery — advance to the next workspace
continue;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- LangSmith mode requires `--dataset` and does not support `--
- LangSmith client not initialized - check LANGSMITH_API_KEY
- Dataset "${datasetName}" not found: ${errorMessage}
- Trace project "${projectName}" not found in the eval workspa
- Thread ${ref.threadId}: the live turn is the first/only user
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/646a361cbdbd5271.
Report an issue: GitHub.