slopus/happy · error

Failed to resume Codex thread ${opts.threadId}: ${reason}

Error message

Failed to resume Codex thread ${opts.threadId}: ${reason}

What it means

resumeExistingThread() wraps any failure from client.resumeThread() — network/transport errors, 'Codex CLI is not installed', invalid thread id, etc. — into a single Error prefixed with 'Failed to resume Codex thread <id>: <reason>'. The original message is preserved after the colon.

Source

Thrown at packages/happy-cli/src/codex/resumeExistingThread.ts:56

            mcpServers: opts.mcpServers,
        });

        opts.session.updateMetadata((currentMetadata) => ({
            ...currentMetadata,
            codexThreadId: resumedThread.threadId,
        }));
        opts.messageBuffer.addMessage(`Resumed thread ${trimIdent(resumedThread.threadId)}`, 'status');
        if (opts.announce !== false) {
            opts.session.sendSessionEvent({
                type: 'message',
                message: `Resumed Codex thread ${resumedThread.threadId}`,
            });
        }

        return resumedThread;
    } catch (error) {
        const reason = error instanceof Error ? error.message : String(error);
        throw new Error(`Failed to resume Codex thread ${opts.threadId}: ${reason}`);
    }
}

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Read the `reason` after the colon to identify the root cause and fix that first
  2. Verify the thread id exists (check Codex session files) and the Codex CLI is installed (`codex --version`)
  3. If the thread is unrecoverable, start a new thread instead of resuming
  4. Retry once on transient transport errors before failing

Example fix

// before
throw new Error(`Failed to resume Codex thread ${id}: ${reason}`); // opaque handling upstream

// after
try {
  await resumeExistingThread({ ... });
} catch (error) {
  if (String(error).includes('not installed')) {
    // prompt user to install Codex CLI
  } else {
    await startNewThread();
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!threadId) {
  await startNewThread();
  return;
}

Try / catch

try {
  await resumeExistingThread({ client, session, messageBuffer, threadId, cwd, mcpServers });
} catch (error) {
  const msg = (error as Error).message;
  if (msg.includes('not installed') || msg.includes('No thread available')) {
    await startNewThread();
  } else {
    throw error; // surface unexpected failures
  }
}

Prevention

When it happens

Trigger: Calling resumeExistingThread() where the underlying resumeThread() throws for any reason: missing Codex CLI, nonexistent/deleted thread id, app-server crash, or IO failure in the target cwd.

Common situations: Resuming a Codex session whose rollout file was deleted; `happy codex --resume` with an id from another machine; Codex CLI upgraded/removed between runs; this wrapper appearing in logs when the root cause is error 60/61.

Related errors


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