slopus/happy · error

No thread available to resume.

Error message

No thread available to resume.

What it means

The client's resumeThread() requires a thread id, taken from opts.threadId or the client's internal _threadId. If neither is set there is no existing Codex conversation to resume, so it throws. Starting a new thread (startThread) is required before resuming.

Source

Thrown at packages/happy-cli/src/codex/codexAppServerClient.ts:831

        this._threadId = result.thread.id;
        this._turnId = null;
        this.rawSubagentActivitySignaturesByItemId.clear();
        this.rememberThreadDefaults(opts);
        logger.debug('[CodexAppServer] Thread started:', this._threadId);
        return { threadId: result.thread.id, model: result.model };
    }

    async resumeThread(opts?: {
        threadId?: string;
        model?: string;
        cwd?: string;
        approvalPolicy?: ApprovalPolicy;
        sandbox?: SandboxMode;
        mcpServers?: Record<string, unknown>;
    }): Promise<{ threadId: string; model: string }> {
        const threadId = opts?.threadId ?? this._threadId;
        if (!threadId) {
            throw new Error('No thread available to resume.');
        }

        const defaults = this.threadDefaults ?? {};
        const params: ResumeConversationParams = {
            threadId,
            model: opts?.model ?? defaults.model ?? null,
            modelProvider: null,
            cwd: opts?.cwd ?? defaults.cwd ?? process.cwd(),
            approvalPolicy: opts?.approvalPolicy ?? defaults.approvalPolicy ?? null,
            sandbox: opts?.sandbox ?? defaults.sandbox ?? null,
            config: this.buildThreadConfig(opts?.mcpServers ?? defaults.mcpServers),
            baseInstructions: null,
            developerInstructions: null,
            persistExtendedHistory: true,
        };

        const result = await this.request('thread/resume', params) as ResumeConversationResponse;
        this._threadId = result.thread.id;

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Pass the thread id explicitly: client.resumeThread({ threadId: '<id>', ... })
  2. Call startThread() first if you intend a new conversation, not a resume
  3. Check session metadata (codexThreadId) actually contains a stored thread id before resuming
  4. Fall back to starting a new thread when no id is available

Example fix

// before
await client.resumeThread({ cwd, mcpServers }); // throws: no thread

// after
const threadId = session.metadata.codexThreadId;
if (threadId) {
  await client.resumeThread({ threadId, cwd, mcpServers });
} else {
  await client.startThread({ cwd, mcpServers });
}
Defensive patterns

Strategy: validation

Validate before calling

const threadId = opts.threadId ?? client.threadId;
if (!threadId) {
  // fall back to a new thread instead of resuming
  await client.startThread({ cwd, mcpServers });
} else {
  await client.resumeThread({ threadId, cwd, mcpServers });
}

Type guard

function hasThreadId(client: { threadId?: string | null }): client is { threadId: string } {
  return typeof client.threadId === 'string' && client.threadId.length > 0;
}

Try / catch

try {
  await client.resumeThread({ threadId, cwd, mcpServers });
} catch (error) {
  if ((error as Error).message === 'No thread available to resume.') {
    await client.startThread({ cwd, mcpServers });
  } else { throw error; }
}

Prevention

When it happens

Trigger: Calling resumeThread() on a freshly constructed CodexAppServerClient with no opts.threadId and before startThread() has ever run, or after the client was reset so _threadId is null.

Common situations: Trying to `happy codex --resume <id>` with a wrong/empty thread id; resuming a side chat whose thread id metadata was never persisted; reconnecting a session whose codexThreadId metadata is missing.

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/e531f985ac3d079f. Report an issue: GitHub.