diff --git a/src/codex/codexMcpClient.ts b/src/codex/codexMcpClient.ts index 0c7097d1..688ab61f 100644 --- a/src/codex/codexMcpClient.ts +++ b/src/codex/codexMcpClient.ts @@ -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 { + logger.debug('[CodexMCP] Force closing session'); + try { + await this.disconnect(); + } finally { + this.clearSession(); + } + logger.debug('[CodexMCP] Session force-closed'); + } + async disconnect(): Promise { if (!this.connected) return; @@ -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`); } } diff --git a/src/codex/runCodex.ts b/src/codex/runCodex.ts index d789bb19..60e2d4da 100644 --- a/src/codex/runCodex.ts +++ b/src/codex/runCodex.ts @@ -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); @@ -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(); @@ -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' }); @@ -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();