Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/codex/codexMcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,21 @@ export class CodexMcpClient {
return this.sessionId;
}

/**
* Force close the Codex MCP transport and clear all session identifiers.
* Use this for permanent shutdown (e.g. kill/exit). Prefer `disconnect()` for
* transient connection resets where you may want to keep the session id.
*/
async forceCloseSession(): Promise<void> {
logger.debug('[CodexMCP] Force closing session');
try {
await this.disconnect();
} finally {
this.clearSession();
}
logger.debug('[CodexMCP] Session force-closed');
}

async disconnect(): Promise<void> {
if (!this.connected) return;

Expand Down Expand Up @@ -327,9 +342,7 @@ export class CodexMcpClient {

this.transport = null;
this.connected = false;
this.sessionId = null;
this.conversationId = null;

logger.debug('[CodexMCP] Disconnected');
// Preserve session/conversation identifiers for potential reconnection / recovery flows.
logger.debug(`[CodexMCP] Disconnected; session ${this.sessionId ?? 'none'} preserved`);
}
}
24 changes: 13 additions & 11 deletions src/codex/runCodex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,7 @@ export async function runCodex(opts: {
}

abortController.abort();
messageQueue.reset();
permissionHandler.reset();
reasoningProcessor.abort();
diffProcessor.reset();
logger.debug('[Codex] Abort completed - session remains active');
} catch (error) {
logger.debug('[Codex] Error during abort:', error);
Expand Down Expand Up @@ -282,6 +279,13 @@ export async function runCodex(opts: {
await session.close();
}

// Force close Codex transport (best-effort) so we don't leave stray processes
try {
await client.forceCloseSession();
} catch (e) {
logger.debug('[Codex] Error while force closing Codex session during termination', e);
}

// Stop caffeinate
stopCaffeinate();

Expand Down Expand Up @@ -691,11 +695,9 @@ export async function runCodex(opts: {
if (isAbortError) {
messageBuffer.addMessage('Aborted by user', 'status');
session.sendSessionEvent({ type: 'message', message: 'Aborted by user' });
// Session was already stored in handleAbort(), no need to store again
// Mark session as not created to force proper resume on next message
wasCreated = false;
currentModeHash = null;
logger.debug('[Codex] Marked session as not created after abort for proper resume');
// Abort cancels the current task/inference but keeps the Codex session alive.
// Do not clear session state here; the next user message should continue on the
// existing session if possible.
} else {
messageBuffer.addMessage('Process exited unexpectedly', 'status');
session.sendSessionEvent({ type: 'message', message: 'Process exited unexpectedly' });
Expand Down Expand Up @@ -738,9 +740,9 @@ export async function runCodex(opts: {
} catch (e) {
logger.debug('[codex]: Error while closing session', e);
}
logger.debug('[codex]: client.disconnect begin');
await client.disconnect();
logger.debug('[codex]: client.disconnect done');
logger.debug('[codex]: client.forceCloseSession begin');
await client.forceCloseSession();
logger.debug('[codex]: client.forceCloseSession done');
// Stop Happy MCP server
logger.debug('[codex]: happyServer.stop');
happyServer.stop();
Expand Down