Skip to content

Conversation

@OmerFarukOruc
Copy link

Summary

  • Fix createSession() in src/tmux.ts to work with the Rust-based Codex CLI (v0.98+)
  • The previous implementation launched codex in interactive TUI mode and typed prompts via tmux send-keys, which fails because the Rust CLI requires a real TTY
  • Switch to codex exec (non-interactive mode) with stdin prompt piping

Problem

With codex-cli v0.98.0 (Rust), every codex-agent start command fails instantly (~2.5s, 0 tokens consumed):

Job started: fc89dbee
tmux session: undefined    ← session never created

Root causes:

  1. Interactive TUI requires a real TTY — tmux detached sessions don't provide one, causing instant process exit
  2. -a never flag doesn't exist in codex exec mode
  3. script command (used for output logging) isn't available on all Linux distros (e.g., Fedora)
  4. tmux send-keys prompt injection is fragile and timing-dependent

Changes

Before (broken) After (fixed)
codex interactive TUI mode codex exec non-interactive mode
Prompt typed via tmux send-keys Prompt piped via stdin (cat prompt | codex exec -)
Output logged via script -q Output logged via tee (universally available)
-a never (invalid for exec) --full-auto / --dangerously-bypass-approvals-and-sandbox
Sleep + send-keys timing hacks No timing dependencies

Test plan

  • Verified codex-agent start "test prompt" -s read-only creates persistent tmux session
  • Verified codex-agent jobs --json shows status: "running" (not failed)
  • Verified codex-agent capture <id> returns agent output
  • Verified agent completes successfully with status: "completed"
  • Tested on Fedora Linux with codex-cli 0.98.0

🤖 Generated with Claude Code

The previous implementation launched `codex` in interactive TUI mode
inside tmux and typed prompts via `tmux send-keys`. This fails with
the Rust-based Codex CLI (v0.98+) because:

1. The interactive TUI requires a real TTY, which tmux's detached
   sessions don't provide — causing instant process exit
2. The `-a never` flag doesn't exist in `codex exec` mode
3. The `script` command used for output logging isn't available on
   all Linux distributions (e.g., Fedora minimal installs)

Changes:
- Switch from interactive `codex` to `codex exec` (non-interactive)
- Pipe prompt via stdin (`cat prompt | codex exec -`) instead of
  fragile `tmux send-keys` typing
- Replace `script` with `tee` for universal output logging
- Use `--full-auto` / `--dangerously-bypass-approvals-and-sandbox`
  instead of `-a never` (correct flags for exec mode)
- Add `--json` flag for structured JSONL output
- Add `-o` flag to save last agent message to file
- Remove all `sleep` + `send-keys` timing hacks (no longer needed)

Tested with codex-cli 0.98.0 on Fedora Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tinycrops
Copy link

tinycrops commented Feb 8, 2026

I was having an issue, too. This fixed it.

● Update(~/.codex-orchestrator/src/tmux.ts)
  ⎿  Added 1 line, removed 1 line
      76      // This allows us to capture the output even after completion                                                                                                                   
      77      // Create detached session that runs codex and stays open after it exits
      78      // Using script to log all terminal output                                                                                                                                      
      79 -    const shellCmd = `script -q "${logFile}" codex ${codexArgs}; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                                 
      79 +    const shellCmd = `script -q -c "codex ${codexArgs}" "${logFile}"; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                            
      80
      81      execSync(
      82        `tmux new-session -d -s "${sessionName}" -c "${options.cwd}" '${shellCmd}'`,

The Bug: In ~/.codex-orchestrator/src/tmux.ts line 79, the script command was using incorrect syntax:

WRONG (missing -c flag)

script -q "${logFile}" codex ${codexArgs}

CORRECT

script -q -c "codex ${codexArgs}" "${logFile}"

Without the -c flag, the script command failed to execute codex properly, causing tmux sessions to exit immediately before codex could start.

The Fix

Updated the tmux.ts file with the correct script command syntax. Tested with a simple agent that successfully ran and responded.

@tinycrops
Copy link

tinycrops commented Feb 8, 2026

● Update(~/.codex-orchestrator/src/tmux.ts)                                                                                                                                                   
  ⎿  Added 4 lines                                                                                                                                                                          
      121        );                                                                                                                                                                           
      122      } else {                                                                                                                                                                     
      123        // For long prompts, use load-buffer approach                                                                                                                                
      124 +      // Codex TUI enters paste mode on multi-line paste - need Escape to exit, then Enter to submit                                                                               
      125        execSync(`tmux load-buffer "${promptFile}"`, { stdio: "pipe" });
      126        execSync(`tmux paste-buffer -t "${sessionName}"`, { stdio: "pipe" });
      127 +      spawnSync("sleep", ["0.5"]);                                                                                                                                          
      128 +      // Escape exits paste mode in the TUI                                                                                                                                 
      129 +      execSync(`tmux send-keys -t "${sessionName}" Escape`, { stdio: "pipe" });                                                                                             
      130        spawnSync("sleep", ["0.3"]);
      131        execSync(`tmux send-keys -t "${sessionName}" Enter`, { stdio: "pipe" });
      132      }

@tinycrops
Copy link

● Update(~/.codex-orchestrator/src/tmux.ts)                                                                                                           
  ⎿  Added 3 lines, removed 4 lines                                                                                                                                                           
      71        `-s`, options.sandbox,                                                                                                                                                        
      72      ].join(" ");                                                                                                                            
      73                                                                                                                                                                                      
      74 -    // Launch codex directly (no script wrapper - more stable)                                                                                                                    
      75 -    // Log output via tmux pipe-pane instead                                                                                                                                        
      76 -    // Keep shell alive after codex exits so we can capture final state                                                                                                      
      77 -    const shellCmd = `codex ${codexArgs} 2>&1 | tee "${logFile}"; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                                
      74 +    // Use script to provide a PTY and log all terminal output                                                                                                               
      75 +    // codex TUI requires a real terminal (not a pipe)                                                                                                                       
      76 +    const shellCmd = `script -q -c "codex ${codexArgs}" "${logFile}"; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                            
      77
      78      execSync(
      79        `tmux new-session -d -s "${sessionName}" -c "${options.cwd}" '${shellCmd}'`,

@tinycrops
Copy link

● Update(~/.codex-orchestrator/src/tmux.ts)             
  ⎿  Added 29 lines, removed 20 lines
       71        `-s`, options.sandbox,                                                                                                                                                       
       72      ].join(" ");
       73                                                                                                                                                                                     
       74 -    // Create tmux session with codex running                                                                                                                               
       75 -    // Use script to capture all output, and keep shell alive after codex exits                                                                                             
       76 -    // This allows us to capture the output even after completion                                                                                                           
       77 -    // Create detached session that runs codex and stays open after it exits                                                                                                
       78 -    // Using script to log all terminal output                                                                                                                              
       79 -    const shellCmd = `script -q -c "codex ${codexArgs}" "${logFile}"; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                           
       74 +    // Launch codex directly (no script wrapper - more stable)                                                                                                              
       75 +    // Log output via tmux pipe-pane instead                                                                                                                                
       76 +    // Keep shell alive after codex exits so we can capture final state                                                                                                     
       77 +    const shellCmd = `codex ${codexArgs} 2>&1 | tee "${logFile}"; echo "\\n\\n[codex-agent: Session complete. Press Enter to close.]"; read`;                               
       78
       79      execSync(
       80        `tmux new-session -d -s "${sessionName}" -c "${options.cwd}" '${shellCmd}'`,
       81        { stdio: "pipe", cwd: options.cwd }
       82      );
       83
       86 -    // Give codex a moment to initialize and show update prompt if any                                                                                                      
       87 -    spawnSync("sleep", ["1"]);                                                                                                                                              
       84 +    // Wait for codex TUI to initialize - poll until the session shows the prompt                                                                                           
       85 +    // indicator (›) which means the TUI is ready for input                                                                                                                 
       86 +    const maxWaitMs = 15000;                                                                                                                                                
       87 +    const pollIntervalMs = 500;                                                                                                                                             
       88 +    let waited = 0;                                                                                                                                                         
       89 +    let tuiReady = false;                                                                                                                                                   
       90
       89 -    // Skip update prompt if it appears by sending "3" (skip until next version)                                                                                            
       90 -    // Then Enter to dismiss any remaining prompts                                                                                                                          
       91 -    execSync(`tmux send-keys -t "${sessionName}" "3"`, { stdio: "pipe" });                                                                                                  
       92 -    spawnSync("sleep", ["0.5"]);                                                                                                                                            
       93 -    execSync(`tmux send-keys -t "${sessionName}" Enter`, { stdio: "pipe" });                                                                                                
       94 -    spawnSync("sleep", ["1"]);                                                                                                                                              
       91 +    while (waited < maxWaitMs) {                                                                                                                                            
       92 +      spawnSync("sleep", [String(pollIntervalMs / 1000)]);                                                                                                                  
       93 +      waited += pollIntervalMs;                                                                                                                                             
       94
       96 -    // Send the prompt (read from file to handle complex prompts)                                                                                                           
       97 -    // Using send-keys with the prompt content                                                                                                                              
       95 +      if (!sessionExists(sessionName)) {                                                                                                                                    
       96 +        return { sessionName, success: false, error: "Codex session exited during startup" };                                                                               
       97 +      }                                                                                                                                                                     
       98 +                                                                                                                                                                            
       99 +      const paneContent = capturePane(sessionName, { lines: 30 });                                                                                                          
      100 +      if (paneContent && paneContent.includes("context left")) {                                                                                                            
      101 +        tuiReady = true;                                                                                                                                                    
      102 +        break;                                                                                                                                                              
      103 +      }                                                                                                                                                                     
      104 +    }                                                                                                                                                                       
      105 +                                                                                                                                                                            
      106 +    if (!tuiReady) {                                                                                                                                                        
      107 +      return { sessionName, success: false, error: `Codex TUI did not initialize within ${maxWaitMs / 1000}s` };                                                            
      108 +    }                                                                                                                                                                       
      109 +                                                                                                                                                                            
      110 +    // Send the prompt - TUI is confirmed ready                                                                                                                             
      111      const promptContent = options.prompt.replace(/'/g, "'\\''"); // Escape single quotes
      112
      100 -    // For very long prompts, we'll type it in chunks or use a different approach                                                                                           
      113      if (options.prompt.length < 5000) {
      102 -      // Send prompt directly for shorter prompts                                                                                                                           
      103 -      // Use separate send-keys calls for text and Enter to ensure Enter is processed                                                                                       
      114        execSync(
      115          `tmux send-keys -t "${sessionName}" '${promptContent}'`,
      116          { stdio: "pipe" }
      117        );
      108 -      // Small delay to let TUI process the text before Enter                                                                                                               
      118        spawnSync("sleep", ["0.3"]);
      119        execSync(
      120          `tmux send-keys -t "${sessionName}" Enter`,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants