mastra-ai/mastra · error

No thread ID available. Start a session before building eval

Error message

No thread ID available. Start a session before building eval context.

What it means

buildEvalContext() needs a conversation thread ID to load messages from Mastra storage and build eval context. It resolves the thread ID from options.threadId or session.thread.getId(); if both yield nothing, no storage lookup is possible and it throws. Evals are meaningless without a persisted thread, so this is a fail-fast guard.

Source

Thrown at mastracode/sdk/src/evals/context-builder.ts:53

  session: Session<any>;
  /** Thread ID to build context for (defaults to current thread) */
  threadId?: string;
  /** Limit messages to the last N turns (user+assistant pairs). Undefined = all messages */
  lastNTurns?: number;
};

/**
 * Build an evaluation context from a AgentController session.
 *
 * This extracts messages from storage, builds trajectory from trace spans,
 * and packages everything into the format scorers expect.
 */
export async function buildEvalContext(options: BuildContextOptions): Promise<MastraCodeEvalContext> {
  const { controller, session, lastNTurns } = options;
  const threadId = options.threadId ?? session.thread.getId();

  if (!threadId) {
    throw new Error('No thread ID available. Start a session before building eval context.');
  }

  const mastra = controller.getMastra();
  const storage = mastra?.getStorage();

  // 1. Get raw MastraDB messages from memory storage
  const rawMessages = await getRawMessages(storage, threadId, lastNTurns);

  // 2. Split messages into input/output categories
  const { inputMessages, systemMessages, outputMessages } = categorizeMessages(rawMessages);

  // 3. Extract trajectory from observability traces
  const { trajectory, traceId } = await extractSessionTrajectory(storage, threadId);

  // 4. Build request context from AgentController state
  const requestContext = buildRequestContext(session, threadId);

  return {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Start a session (so a thread is created and persisted) before calling buildEvalContext.
  2. Pass an explicit threadId: buildEvalContext({ controller, session, threadId: existingThreadId }).
  3. Fetch a valid thread ID from storage/memory beforehand and assert it is truthy before invoking.

Example fix

// before
await buildEvalContext({ controller, session });
// after
const threadId = session.thread.getId?.();
if (!threadId) throw new Error('start a session first');
await buildEvalContext({ controller, session, threadId });
Defensive patterns

Strategy: validation

Validate before calling

const threadId = options.threadId ?? session.thread?.getId?.();
if (!threadId) {
  throw new Error('Start a session to obtain a thread ID before building eval context.');
}

Type guard

function hasThreadId(s: { thread?: { getId?: () => string | undefined } }): s is { thread: { getId: () => string } } {
  return typeof s.thread?.getId?.() === 'string' && s.thread.getId().length > 0;
}

Try / catch

try {
  ctx = await buildEvalContext({ controller, session });
} catch (err) {
  if (err instanceof Error && err.message.includes('No thread ID available')) {
    await session.start(); // create the thread, then retry once
    ctx = await buildEvalContext({ controller, session });
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling buildEvalContext({ controller, session }) where session.thread.getId() returns undefined/null and no options.threadId was supplied — typically invoking evals before the session has started a thread.

Common situations: Running evals in a script immediately after constructing a controller without calling a session-start API; passing a stale/detached session object whose thread was never persisted; wiring evals into a pipeline that only runs after an aborted turn.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/b629d13c4fa4c557. Report an issue: GitHub.