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 = runs

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Confirm the trace still exists by running `client.listRuns({ projectName, filter: 'eq(thread_id, "<id>")' })` directly and check it returns runs.
  2. Verify the sourceProject string matches the LangSmith project exactly (case-sensitive, no trailing whitespace).
  3. If the trace aged out, re-record the conversation in LangTracer to produce a fresh trace and update the case's threadId reference.
  4. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/646a361cbdbd5271. Report an issue: GitHub.