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
- Pass the thread id explicitly: client.resumeThread({ threadId: '<id>', ... })
- Call startThread() first if you intend a new conversation, not a resume
- Check session metadata (codexThreadId) actually contains a stored thread id before resuming
- 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
- Persist codexThreadId in session metadata immediately after thread creation
- Only call resumeThread when a stored thread id exists
- Default to startThread for brand-new sessions
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
- No active thread. Call startThread first.
- No active Codex thread
- Failed to resume Codex thread ${opts.threadId}: ${reason}
- Backend has been disposed
- Session not started
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/e531f985ac3d079f.
Report an issue: GitHub.