Tech 9 min read

Letting Claude Code and Codex Run Overnight in tmux (Practice)

Recap

In the setup article, I described the idea and basic scripts for linking Claude Code and Codex.

Claude Code (implement)
    ↓ code output
Codex (review)
    ↓ review result
Claude Code (fix)
    ↓ repeat...

This time I actually ran it.


Scripts Used

Four scripts in total.

start-tmux.sh (Launch Script)

Starts a tmux session split left/right with workers in each pane.

#!/bin/bash
# tmux session launch script
# Left: Claude Code (implement) / Right: Codex (review)

PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SESSION_NAME="my-dev"  # change to whatever you want
MAX_ROUNDS=${1:-10}

# Check for existing session
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
    echo "Session '$SESSION_NAME' already exists"
    echo "Attach: tmux attach -t $SESSION_NAME"
    echo "Delete: tmux kill-session -t $SESSION_NAME"
    exit 1
fi

# Create log directory / clear signal files
mkdir -p "$PROJECT_DIR/logs"
rm -f "$PROJECT_DIR/logs/.commit_done" "$PROJECT_DIR/logs/.review_done"

# Grant execute permission
chmod +x "$PROJECT_DIR/scripts/claude-worker.sh"
chmod +x "$PROJECT_DIR/scripts/codex-worker.sh"

# Create tmux session
tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_DIR"

# Left pane: Claude Code worker
tmux send-keys -t "$SESSION_NAME" "./scripts/claude-worker.sh $MAX_ROUNDS" C-m

# Right pane: Codex worker
tmux split-window -h -t "$SESSION_NAME" -c "$PROJECT_DIR"
tmux send-keys -t "$SESSION_NAME" "./scripts/codex-worker.sh" C-m

# Attach to session
echo "Starting tmux session '$SESSION_NAME'..."
echo "Left: Claude Code (implement) / Right: Codex (review)"
tmux attach -t "$SESSION_NAME"

claude-worker.sh (Left Pane)

Claude Code handles implementation. One task per round; waits for Codex after each commit.

#!/bin/bash
# Claude Code worker (left pane)
# implement → commit → wait for review → fix loop

set -e

PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
LOG_DIR="$PROJECT_DIR/logs"
CLAUDE_LOG="$LOG_DIR/claude.log"
REVIEW_FILE="$LOG_DIR/review.md"
COMMIT_SIGNAL="$LOG_DIR/.commit_done"
REVIEW_SIGNAL="$LOG_DIR/.review_done"
MAX_ROUNDS=${1:-10}

mkdir -p "$LOG_DIR"

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

cd "$PROJECT_DIR"

# Initialize signal files
rm -f "$COMMIT_SIGNAL" "$REVIEW_SIGNAL"

echo -e "${GREEN}=== Claude Code worker started ===${NC}"

for ((round=1; round<=MAX_ROUNDS; round++)); do
    echo -e "${YELLOW}=== Round $round/$MAX_ROUNDS ===${NC}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Round $round started" >> "$CLAUDE_LOG"

    # Step 1: Implement
    echo -e "${CYAN}[Implementing...]${NC}"
    claude --dangerously-skip-permissions -p "
You are developing ○○. (describe what you're building here)

## Instructions
1. Check docs/todo.md for incomplete tasks
2. Pick the next incomplete task and implement it
3. After implementation, record in docs/progress.md
4. git commit the changes (concise message)
5. git push
6. Mark the task as complete in docs/todo.md

## Constraints
- Make decisions without asking questions
- Fix errors on your own
- Focus on one task per round

Start implementing.
" 2>&1 | tee -a "$CLAUDE_LOG"

    # Commit complete signal
    touch "$COMMIT_SIGNAL"
    echo -e "${GREEN}[Commit done - waiting for Codex review...]${NC}"

    # Wait for review to complete
    while [ ! -f "$REVIEW_SIGNAL" ]; do
        sleep 2
    done
    rm -f "$REVIEW_SIGNAL"

    # Step 2: Check review result / fix if needed
    if [ -f "$REVIEW_FILE" ] && ! grep -qi "LGTM" "$REVIEW_FILE"; then
        echo -e "${CYAN}[Review feedback received - fixing...]${NC}"
        claude --dangerously-skip-permissions -p "
Received the following review from Codex.
Fix the issues and commit and push again.

$(cat "$REVIEW_FILE")
" 2>&1 | tee -a "$CLAUDE_LOG"
    else
        echo -e "${GREEN}[LGTM - no fixes needed]${NC}"
    fi

    # Step 3: Code Simplifier
    echo -e "${CYAN}[Running Code Simplifier...]${NC}"
    claude --dangerously-skip-permissions -p "
/plugin run code-simplifier

Clean up and simplify recently changed code.
Improve readability and maintainability without changing functionality.
Commit and push if there are changes.
" 2>&1 | tee -a "$CLAUDE_LOG"

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Round $round complete" >> "$CLAUDE_LOG"

    # Check if all tasks are done
    if ! grep -q "\- \[ \]" "$PROJECT_DIR/docs/todo.md" 2>/dev/null; then
        echo -e "${GREEN}All tasks complete!${NC}"
        break
    fi

    sleep 2
done

echo -e "${GREEN}=== Claude Code worker finished ===${NC}"

codex-worker.sh (Right Pane)

Detects commits and runs Codex reviews.

#!/bin/bash
# Codex worker (right pane)
# detect commit → run review loop

set -e

PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
LOG_DIR="$PROJECT_DIR/logs"
CODEX_LOG="$LOG_DIR/codex.log"
REVIEW_FILE="$LOG_DIR/review.md"
COMMIT_SIGNAL="$LOG_DIR/.commit_done"
REVIEW_SIGNAL="$LOG_DIR/.review_done"

mkdir -p "$LOG_DIR"

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

cd "$PROJECT_DIR"

echo -e "${GREEN}=== Codex worker started ===${NC}"
echo "Waiting for commits..."

while true; do
    # Wait for commit signal
    if [ -f "$COMMIT_SIGNAL" ]; then
        rm -f "$COMMIT_SIGNAL"

        echo -e "${YELLOW}=== New commit detected ===${NC}"
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Review started" >> "$CODEX_LOG"

        echo -e "${CYAN}[Reviewing...]${NC}"
        codex review --commit HEAD 2>&1 | tee "$REVIEW_FILE" | tee -a "$CODEX_LOG"

        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Review complete" >> "$CODEX_LOG"

        # Review complete signal
        touch "$REVIEW_SIGNAL"
        echo -e "${GREEN}[Review done]${NC}"
        echo ""
        echo "Waiting for commits..."
    fi

    sleep 2
done

ai-loop.sh (Simple Version)

A simpler version without tmux that runs sequentially.

#!/bin/bash
# AI auto-dev loop script
# Claude Code implement → Codex review → Claude Code fix

set -e

# Config
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
LOG_DIR="$PROJECT_DIR/logs"
CLAUDE_LOG="$LOG_DIR/claude.log"
CODEX_LOG="$LOG_DIR/codex.log"
REVIEW_FILE="$LOG_DIR/review.md"
MAX_ROUNDS=${1:-10}

# Create log directory
mkdir -p "$LOG_DIR"

# Colored output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

echo -e "${GREEN}=== AI auto-dev loop started ===${NC}"
echo "Project: $PROJECT_DIR"
echo "Max rounds: $MAX_ROUNDS"
echo ""

cd "$PROJECT_DIR"

for ((round=1; round<=MAX_ROUNDS; round++)); do
    echo -e "${YELLOW}=== Round $round/$MAX_ROUNDS ===${NC}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Round $round started" >> "$CLAUDE_LOG"

    # Step 1: Claude Code implements
    echo -e "${CYAN}[Step 1] Claude Code implementing...${NC}"
    claude --dangerously-skip-permissions -p "
You are developing ○○. (describe what you're building here)

## Instructions
1. Check docs/todo.md for incomplete tasks
2. Pick the next incomplete task and implement it
3. After implementation, record in docs/progress.md
4. git commit the changes (concise message)
5. git push
6. Mark the task as complete in docs/todo.md

## Constraints
- Make decisions without asking questions
- Fix errors on your own
- Focus on one task per round

Start implementing.
" 2>&1 | tee -a "$CLAUDE_LOG"

    # Step 2: Codex review
    echo -e "${CYAN}[Step 2] Codex reviewing...${NC}"
    codex review --commit HEAD 2>&1 | tee "$REVIEW_FILE" | tee -a "$CODEX_LOG"

    # Step 3: Check review and fix if needed
    if ! grep -qi "LGTM" "$REVIEW_FILE"; then
        echo -e "${CYAN}[Step 3] Claude Code fixing...${NC}"
        claude --dangerously-skip-permissions -p "
Received the following review from Codex.
Fix the issues and commit and push again.

$(cat "$REVIEW_FILE")
" 2>&1 | tee -a "$CLAUDE_LOG"
    else
        echo -e "${GREEN}[Step 3] Review OK - no fixes needed${NC}"
    fi

    # Step 4: Code Simplifier cleanup
    echo -e "${CYAN}[Step 4] Running Code Simplifier...${NC}"
    claude --dangerously-skip-permissions -p "
/plugin run code-simplifier

Clean up and simplify recently changed code.
Improve readability and maintainability without changing functionality.
Commit and push if there are changes.
" 2>&1 | tee -a "$CLAUDE_LOG"

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Round $round complete" >> "$CLAUDE_LOG"
    echo ""

    # Check if all tasks are done
    if ! grep -q "\- \[ \]" "$PROJECT_DIR/docs/todo.md" 2>/dev/null; then
        echo -e "${GREEN}All tasks complete!${NC}"
        break
    fi

    # Interval
    sleep 3
done

echo -e "${GREEN}=== Loop finished ===${NC}"

If you copy-paste this, you’ll need:

claude plugin install code-simplifier

first. It works without it, though.


In Action

The tmux session running left/right split.

AI loop running in tmux split view

Claude implements and commits in the left pane; Codex reviews and sends feedback in the right pane.

Codex review output

When Codex outputs “LGTM,” Claude skips fixes and moves to the next task. When there are issues, Claude fixes first, then advances.


Results

Input Spec

Fed in the soba-eating game spec. A spec covering a two-gauge system, seating system, enemy system, etc.

Generated Code

  • 1,134 lines of TypeScript
  • Vite project that runs
  • Playable in the browser

Completed Tasks

A portion of what was logged in docs/progress.md:

### Basic HTML Structure
- [x] Game screen layout implemented
- [x] Two-gauge UI (fullness + soba satisfaction)
- [x] Timer display
- [x] NEXT display area
- [x] Seat assignment display area

### Player Movement
- [x] Player state interface defined
- [x] Keyboard input handling (arrow keys + WASD)
- [x] 8-direction movement

### Food System
- [x] Food parameters configured (soba regular/large/extra, udon, ramen, napolitan)
- [x] Stage-based spawn rates
- [x] Counter placement and pickup

### Seating System
- [x] Stage-based table selection rules
- [x] Table assignment system
- [x] Eating system

### Enemy System
- [x] Stage-based enemy parameters
- [x] Player-enemy collision detection
- [x] Food dropped when carrying and hit

What Worked Well

File-Based History

Setting up CLAUDE.md to say “record progress in docs/progress.md” paid off. Even when Claude’s context gets compacted, it can re-read the file to catch up on what happened.

Work Unit Control

The prompt says “focus on one task per round,” but sometimes Claude groups multiple tasks into one phase. It’s unpredictable — just how it feels, I guess.

Bug Detection by Codex

Codex caught a bug where “the seat isn’t released when food is dropped,” and Claude fixed it. It was also recorded in progress.md:

### Bug Fixes
- [x] Fixed missing seat release when food is dropped
  - Reset assigned seat state to 'available' in dropCarriedFood()
  - Response to Codex review issue (P1)

Tips

Anything not in the spec gets filled in with whatever seems reasonable. To avoid that, you need a very detailed spec. Have Claude use AskUserQuestionTool in a separate window to ask everything exhaustively until there are no open questions.

The script caps at 10 rounds — adjust at your own discretion.

Pushing after every commit was intentional:

  1. Visibility: you can see what’s happening even from elsewhere
  2. External intervention: --dangerously-skip-permissions gives full access, so the agent could theoretically make unexpected external connections. If that happens, you can modify CLAUDE.md or TODO.md and push to GitHub from wherever you are. When the agent tries to push and encounters a conflict, it has to pull — and pulling loads the updated instructions. Since each round starts a new Claude, the updated instructions take effect on the next round.

This actually stopped an SFTP external connection attempt.


Claude Code + Codex auto-loop works. With a solid spec and task list, it runs itself. I tried leaving it overnight and it finished in under an hour.