TUI turn interrupt failure can lose session context

Open 💬 5 comments Opened May 26, 2026 by 0xZOne
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.133.0

What operating system are you using?

Windows 11 / Windows NT 10.0.22631.0

Shell: Windows PowerShell 5.1.22621.4249

What issue are you seeing?

The Codex TUI can fail an active turn interrupt with:

Error: turn/interrupt failed in TUI

After this happens, the active session loses usable conversation context. Continuing from the TUI behaves like the prior turn/session state is no longer recoverable, so the user has to restart or reconstruct the context manually.

This is particularly painful during long coding sessions because the failure happens while trying to interrupt/steer an active turn, and the resulting context loss discards the working state that the user expected Codex to preserve.

Expected behavior

Interrupting an active turn from the TUI should either:

  • cancel/interrupt the active turn and keep the existing thread context usable, or
  • fail gracefully with a recoverable state and clear instructions.

It should not leave the TUI in a state where the conversation context is lost.

Actual behavior

  • The TUI displays Error: turn/interrupt failed in TUI.
  • The active turn/session context is no longer usable afterward.
  • The exact UI-visible error string does not appear to be persisted verbatim in ~/.codex/log/codex-tui.log.
  • Around the observed failure window, the log shows new session_loop thread ids being created, which matches the user-visible context-loss behavior.

Reproduction notes

I do not yet have a minimal deterministic reproduction. The observed setup was:

  1. Start codex in the TUI on Windows from a large repository.
  2. Run a long coding turn with tool calls.
  3. Interrupt the active turn from the TUI.
  4. Observe Error: turn/interrupt failed in TUI.
  5. Try to continue the session and find that the previous context is lost/unusable.

Environment details

  • Codex CLI: 0.133.0
  • OS: Microsoft Windows NT 10.0.22631.0
  • Shell: Windows PowerShell 5.1.22621.4249
  • Workspace type: large Chromium-derived repository
  • Model in the surrounding logged turn: gpt-5.5 with reasoning_effort=xhigh

Related issues

This looks related to the broader TUI/app-server active-turn state handling class of issues, but the symptom here is specifically the local TUI interrupt path failing with turn/interrupt failed in TUI and causing user-visible context loss.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #23160

Powered by Codex Action

etraut-openai contributor · 3 months ago

If you see this happen again, please use /feedback to upload the logs, then post the session ID here.

jshaofa-ui · 3 months ago

1|# Codex #24518: TUI Turn Interrupt Failure Loses Session Context
2|
3|## Issue Summary
4|
5|The Codex TUI can fail an active turn interrupt with Error: turn/interrupt failed in TUI. After this happens, the active session loses usable conversation context. Continuing from the TUI behaves like the prior turn/session state is no longer recoverable.
6|
7|Environment: Codex CLI 0.133.0, Windows 11, PowerShell 5.1
8|Repro: Long coding turn with tool calls → interrupt from TUI → context lost
9|
10|## Root Cause Analysis
11|
12|### Primary Cause: Interrupt Handler State Corruption
13|
14|When a turn interrupt is triggered from the TUI, the interrupt handler likely:
15|1. Sends an interrupt signal to the active session loop
16|2. The session loop encounters an error during interrupt processing
17|3. Instead of gracefully pausing and preserving state, the error causes the session loop to exit or reset
18|4. New session_loop thread IDs are created (observed in logs), indicating the old session context was abandoned
19|
20|The key issue is that the interrupt failure path doesn't preserve the conversation state — it either:
21|- Clears the message history
22|- Creates a new session context without carrying over the previous state
23|- Fails to reconnect to the existing session
24|
25|### Secondary Cause: Windows-Specific Interrupt Handling
26|
27|The issue appears to be Windows-specific (observed on Windows 11 / PowerShell). Windows handles process interrupts differently from Unix:
28|- Unix: SIGINT/SIGTERM can be caught and handled gracefully
29|- Windows: Ctrl+C handling in console applications is more complex, especially with child processes
30|
31|The TUI's interrupt mechanism may not properly handle Windows' console control event model, leading to the interrupt failing and the session entering an unrecoverable state.
32|
33|## Proposed Fix
34|
35|### Fix 1: Preserve Session State on Interrupt Failure (Critical)
36|
37|``typescript
38|class SessionManager {
39| private lastStableState: SessionState | null = null;
40|
41| async interruptTurn(sessionId: string): Promise<InterruptResult> {
42| const session = this.getActiveSession(sessionId);
43|
44| // Save current state before attempting interrupt
45| const savedState = session.saveCheckpoint();
46| this.lastStableState = savedState;
47|
48| try {
49| // Attempt interrupt
50| await session.interrupt();
51| return { success: true, state: 'interrupted' };
52| } catch (error) {
53| console.error(
Turn interrupt failed: ${error.message});
54|
55| // CRITICAL: Restore from saved state instead of losing context
56| if (this.lastStableState) {
57| try {
58| await session.restoreCheckpoint(this.lastStableState);
59| return {
60| success: false,
61| state: 'restored',
62| error: error.message,
63| message: 'Interrupt failed but session context was preserved'
64| };
65| } catch (restoreError) {
66| console.error(
State restore also failed: ${restoreError.message});
67| }
68| }
69|
70| // Last resort: try to reconnect to existing session
71| return {
72| success: false,
73| state: 'lost',
74| error: error.message,
75| message: 'Interrupt failed and session context may be lost. Try /resume to recover.'
76| };
77| }
78| }
79|}
80|
`
81|
82|### Fix 2: Graceful Interrupt with State Preservation
83|
84|
`typescript
85|class TurnInterruptHandler {
86| async handleInterrupt(session: Session): Promise<void> {
87| // Step 1: Signal the session to stop (non-destructive)
88| session.requestStop();
89|
90| // Step 2: Wait for graceful stop with timeout
91| const stopped = await session.waitForStop({ timeout: 5000 });
92|
93| if (!stopped) {
94| // Step 3: If graceful stop fails, force stop but preserve state
95| const checkpoint = session.saveCheckpoint();
96| await session.forceStop();
97|
98| // Step 4: Create new session loop from checkpoint
99| const newSession = await this.createSessionFromCheckpoint(checkpoint);
100| this.replaceActiveSession(session.id, newSession);
101|
102| // Step 5: Notify user
103| this.ui.showNotification({
104| type: 'warning',
105| message: 'Turn interrupted. Session context preserved. You can continue from here.'
106| });
107| } else {
108| // Graceful stop succeeded
109| this.ui.showNotification({
110| type: 'info',
111| message: 'Turn interrupted successfully.'
112| });
113| }
114| }
115|}
116|
`
117|
118|### Fix 3: Windows-Specific Console Control Handling
119|
120|
`typescript
121|// Windows: Handle console control events properly
122|if (process.platform === 'win32') {
123| process.on('SIGINT', () => {
124| // On Windows, SIGINT may come from Ctrl+C
125| // Don't exit — instead trigger graceful interrupt
126| sessionManager.interruptCurrentTurn()
127| .catch(err => console.error('Interrupt failed:', err));
128| });
129|
130| // Also handle Windows-specific console control events
131| const rl = require('readline');
132| rl.createInterface({
133| input: process.stdin,
134| output: process.stdout
135| }).on('SIGINT', () => {
136| sessionManager.interruptCurrentTurn()
137| .catch(err => console.error('Interrupt failed:', err));
138| });
139|}
140|
`
141|
142|### Fix 4: Add /resume Command for Context Recovery
143|
144|
`typescript
145|// Allow users to recover from lost context
146|cli.command('/resume [sessionId]', 'Resume a session from its last checkpoint')
147| .action(async (sessionId?: string) => {
148| const targetId = sessionId || sessionManager.getCurrentSessionId();
149| const checkpoint = await sessionManager.findLatestCheckpoint(targetId);
150|
151| if (!checkpoint) {
152| console.error('No checkpoint found for this session.');
153| return;
154| }
155|
156| const recovered = await sessionManager.restoreFromCheckpoint(checkpoint);
157| console.log(
Session recovered from checkpoint at ${checkpoint.timestamp});
158| console.log(
Messages preserved: ${checkpoint.messageCount});
159| });
160|
``
161|
162|## Implementation Priority
163|
164|1. P0 (Critical): Preserve session state on interrupt failure — prevents context loss
165|2. P1 (Important): Graceful interrupt with state preservation — proper interrupt handling
166|3. P1 (Important): Windows console control handling — fixes platform-specific issue
167|4. P2 (Enhancement): /resume command — gives users a recovery path
168|
169|## Testing Plan
170|
171|1. Interrupt tests: Long turn → interrupt → verify context preserved
172|2. Failure tests: Force interrupt failure → verify state restoration
173|3. Windows tests: Verify Ctrl+C handling on Windows 11 / PowerShell
174|4. Recovery tests: /resume command → verify session restored from checkpoint
175|
176|## Impact
177|
178|- User impact: CRITICAL — Users lose hours of conversation context
179|- Scope: Medium — Affects TUI users on Windows, especially during long coding sessions
180|- Fix complexity: MEDIUM — State management + platform-specific handling, ~250 lines
181|- Related: This is a regression in 0.133.0 (worked in earlier versions)
182|

jshaofa-ui · 3 months ago

Solution: Fix TUI Turn Interrupt Context Loss

Root Cause

When the TUI turn interrupt fails, the session's conversation context is lost. The interrupt handler likely clears local state before confirming the server-side interrupt succeeded.

Fix

Implement atomic interrupt with state checkpoint/restore:

async handleInterrupt(session: Session): Promise<void> {
    const checkpoint = session.createCheckpoint(); // Save state first
    
    try {
        const result = await session.appServer.interrupt({ timeout: 5000 });
        if (result.status === 'interrupted') {
            session.clearTurnState(); // Only clear after server confirms
            this.renderInterruptedState();
        } else {
            this.showError(`Interrupt rejected: ${result.reason}`);
        }
    } catch (error) {
        session.restoreCheckpoint(checkpoint); // Restore on failure
        this.showError(`Interrupt failed: ${error.message}`);
    }
}

Why This Works

  • Atomic operation: State is saved before any changes
  • Server-first: Only clear local state after server confirms
  • Rollback on failure: Checkpoint restore ensures no context loss
shleder · 14 days ago

The failed TUI interrupt followed by unusable prior context is a good real interrupted-session case, especially since new session_loop IDs appeared around the same failure window.

I’m testing whether Codex Rescue can identify a durable incomplete tail or unfinished tool boundary after this kind of failure. It cannot recreate state that was never written or fix the TUI interrupt path. It is local-first, leaves the original rollout untouched, and never automatically replays an unknown command.

If the affected session still exists locally, could you try:

pipx install codex-rescue==0.1.0a3
codex-rescue sessions
codex-rescue doctor --latest

Sanitized output is enough—please don’t share raw JSONL, databases, prompts, repository contents, command output, paths, or secrets.

https://github.com/shleder/codex-rescue