slopus/happy · error

No active Codex thread

Error message

No active Codex thread

What it means

Inside the 'goal-action' RPC handler, after parsing succeeds, the code reads client.threadId to know which thread the action targets. If it is null/undefined the session has no active Codex thread, so the action cannot be applied and it throws.

Source

Thrown at packages/happy-cli/src/codex/runCodex.ts:631

                threadId,
                goal: result.goal,
            });
            messageBuffer.addMessage('Goal updated', 'status');
            return true;
        } catch (error) {
            logger.debug('[Codex] Goal command API failed; falling back to normal turn:', error);
            return false;
        }
    };
    session.rpcHandlerManager.registerHandler('goal-action', async (params: Record<string, unknown>) => {
        const command = parseCodexGoalActionParams(params);
        if (!command) {
            throw new Error('Unsupported Codex goal action');
        }

        const threadId = client.threadId;
        if (!threadId) {
            throw new Error('No active Codex thread');
        }

        const handled = await handleCodexGoalCommand(command, threadId);
        if (!handled) {
            throw new Error('Codex goal actions are not supported by this runtime');
        }

        return { ok: true };
    });

    // Approval handler: routes server → client approval requests to our permission handler
    client.setApprovalHandler(async (params) => {
        const toolName = params.type === 'exec'
            ? 'CodexBash'
            : params.type === 'patch'
                ? 'CodexPatch'
                : (params.toolName ?? 'McpTool');
        const input = params.type === 'exec'

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Ensure the Codex thread is started/resumed before issuing goal actions
  2. Reconnect and resume the thread, then retry the action
  3. Guard the client UI so goal controls are disabled until threadId exists
  4. Catch this in the RPC layer and return a 'no active thread' status to the app

Example fix

// before
await rpc('goal-action', { action: 'pause' }); // throws when no thread

// after
if (client.threadId) {
  await rpc('goal-action', { action: 'pause' });
}
Defensive patterns

Strategy: validation

Validate before calling

if (!client.threadId) {
  throw new RpcError('SESSION_NOT_READY', 'Codex thread not started yet; retry after initialization');
}
await rpc('goal-action', params);

Type guard

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

Try / catch

try {
  await handleGoalAction(params);
} catch (error) {
  if ((error as Error).message === 'No active Codex thread') {
    // reply to RPC with a retryable 'thread not ready' error
  } else { throw error; }
}

Prevention

When it happens

Trigger: A goal-action RPC arrives before startThread/resumeThread completed, or after the thread was closed/lost during reconnection.

Common situations: User taps a goal control in the mobile app while the Codex session is still initializing or already dead; reconnection left client._threadId unset; goal action sent to a session that was started with Claude, not Codex.

Related errors


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