TUI turn interrupt failure can lose session context
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_loopthread 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:
- Start
codexin the TUI on Windows from a large repository. - Run a long coding turn with tool calls.
- Interrupt the active turn from the TUI.
- Observe
Error: turn/interrupt failed in TUI. - 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.5withreasoning_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.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
If you see this happen again, please use
/feedbackto upload the logs, then post the session ID here.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_loopthread IDs are created (observed in logs), indicating the old session context was abandoned19|
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|``
typescriptTurn interrupt failed: ${error.message}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(
);State restore also failed: ${restoreError.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(
);`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|
typescriptSession recovered from checkpoint at ${checkpoint.timestamp}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(
);Messages preserved: ${checkpoint.messageCount}158| console.log(
);``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|
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:
Why This Works
The failed TUI interrupt followed by unusable prior context is a good real interrupted-session case, especially since new
session_loopIDs 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.0a3codex-rescue sessionscodex-rescue doctor --latestSanitized output is enough—please don’t share raw JSONL, databases, prompts, repository contents, command output, paths, or secrets.
https://github.com/shleder/codex-rescue