🔁

CodexCLI がすぐ諦める問題を無限ループで殴る

に公開

やりたいこと
CodexCLIに ToDoリストを渡したら、諦めずに最後まで実装させたい。1回で終わらないなら何回でもお願いしたい。WSL での動作確認です。


なぜ必要だったか

CodexCLI で todolist.md の実装を頼むと、途中までで満足して終わりがち。これはコンテキストの問題もあっていい面もあるんだけど、結局ただ /new todolist.md にしたがって実装して と繰り返すのがだるい。

→ 自動化してみた。


前提環境

  • WSL2 (Windows Subsystem for Linux) で動作確認
  • CodexCLI がインストール済み(codex コマンドが使える状態)
  • bash が使える環境

Windows の通知機能も使ってるので、WSL からの PowerShell 呼び出しが前提になっている部分がある(消しても動きます)。


使い方

./codex-todo-loop.sh /path/to/project /path/to/todolist.md

環境変数で挙動を変えられる:

# 30回まで頑張ってもらう(デフォ20)
MAX_ITERS=30 ./codex-todo-loop.sh /path/to/project /path/to/todolist.md

# 進捗をGitコミット
COMMIT=true ./codex-todo-loop.sh /path/to/project /path/to/todolist.md

# 10秒ごとに残タスク数表示(生存確認)
STATUS_INTERVAL=10 ./codex-todo-loop.sh /path/to/project /path/to/todolist.md

スクリプト

#!/usr/bin/env bash
# codex-todo-loop.sh
# Usage:
#   ./codex-todo-loop.sh <WORKDIR> <todolist.md> [MAX_ITERS]
# Example:
#   ./codex-todo-loop.sh /home/fukuchi/dev/omrb /home/fukuchi/dev/omrb/docs/todolist.md

set -euo pipefail

if [[ $# -lt 2 ]]; then
  echo "Usage: $0 <WORKDIR> <Todolist.md> [MAX_ITERS]" >&2
  exit 1
fi

WORKDIR="$(realpath "$1")"
TODO_MD="$(realpath "$2")"
MAX_ITERS="${3:-20}"
PAUSE_SEC="${PAUSE_SEC:-2}"
TIMEOUT_SECS="${TIMEOUT_SECS:-420}"
COMMIT="${COMMIT:-false}"               # 既定 false
STREAM="${STREAM:-on}"                   # on: codex出力をターミナルへも流す
STATUS_INTERVAL="${STATUS_INTERVAL:-0}"  # >0ならN秒ごとに残数を表示(ハートビート)

need() { command -v "$1" >/dev/null 2>&1 || { echo "missing: $1" >&2; exit 2; }; }
need codex; need awk; need sed; need grep
command -v git >/dev/null 2>&1 || COMMIT=false

cd "$WORKDIR"
LOG_DIR="${LOG_DIR:-$WORKDIR/.codex-todo-logs}"
mkdir -p "$LOG_DIR"

notify_win() {
  local msg="$1"
  local ps_cli="/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
  local win_user="${WINDOWS_USER:-$USER}"  # 環境変数で切り替え
  local ps_script="/mnt/c/Users/${win_user}/Scripts/codex-notify.ps1"
  if [[ -x "$ps_cli" && -f "$ps_script" ]]; then
    "$ps_cli" -NoProfile -ExecutionPolicy Bypass -File "$ps_script" -Message "$msg" -Sound "Alarm5" >/dev/null 2>&1 || true
  else
    "$ps_cli" -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('$msg','codex-todo-loop') | Out-Null" >/dev/null 2>&1 || true
  fi
}

unchecked_count() {
  { grep -E '^\s*[-*]\s*\[ \]' "$TODO_MD" || true; } | wc -l | tr -d ' '
}
has_unchecked()   { [[ "$(unchecked_count)" -gt 0 ]]; }
progress_made()   { local before="$1"; local after; after="$(unchecked_count)"; [[ "$after" -lt "$before" ]]; }
snapshot_hash()   { sha1sum "$TODO_MD" | awk '{print $1}'; }

ensure_repo() {
  if command -v git >/dev/null 2>&1 && ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    git init -q
  fi
}
$COMMIT && ensure_repo

# ハートビート(バックグラウンド)
heartbeat_pid=""
start_heartbeat() {
  [[ "$STATUS_INTERVAL" -gt 0 ]] || return 0
  (
    while :; do
      printf "[status] %(%Y-%m-%d %H:%M:%S)T remaining=%s\r" -1 "$(unchecked_count)"
      sleep "$STATUS_INTERVAL"
    done
  ) >&2 &
  heartbeat_pid=$!
}
stop_heartbeat() {
  [[ -n "$heartbeat_pid" ]] && kill "$heartbeat_pid" >/dev/null 2>&1 || true
  heartbeat_pid=""
  echo >&2
}

# --- preflight ---
if [[ ! -f "$TODO_MD" ]]; then
  echo "[error] todo file not found: $TODO_MD" >&2
  exit 2
fi

echo "[start] workdir=$WORKDIR todo=$TODO_MD max_iters=$MAX_ITERS"
if ! has_unchecked; then
  echo "[preflight] no unchecked checklist items found in $TODO_MD (nothing to do)."
  exit 0
fi

# --- main loop ---
iter=0
start_heartbeat
trap stop_heartbeat EXIT

while has_unchecked && [[ $iter -lt $MAX_ITERS ]]; do
  iter=$((iter+1))
  ts="$(date +%Y%m%d-%H%M%S)"
  log="$LOG_DIR/run-$ts-i$(printf '%02d' "$iter").log"

  before_unchecked="$(unchecked_count)"
  before_hash="$(snapshot_hash)"

  PROMPT=$(cat <<'EOF'
あなたは「与えられたリポジトリ内で、todolist.md のチェックボックスに従って実装・検証し、完了行のみ [x] に更新するエージェント」です。
【厳守】
1) 実際に動作確認できたタスクのみ、その行を - [ ] → - [x] に変更。未完了は [ ] のまま。
2) 実行/検証はリポ標準(make / npm scripts / docker compose 等)を優先。
3) 完了不可な場合は行末に <!-- BLOCKER: ... --> を追記して残す。
4) 変更は小さく安全に。テストがあるなら実行して結果を反映。
5) 保存を確実に行い、作業を終了する。
【作業ディレクトリ】<<WORKDIR>>
【ToDoファイル】<<TODO_MD>>

以下がToDoです。できるところまで実装し、完了した行だけ [x] に更新してください。

---- todolist begin ----
<<TODO_CONTENT>>
---- todolist end ----
EOF
)

  PROMPT="${PROMPT//<<WORKDIR>>/$WORKDIR}"
  PROMPT="${PROMPT//<<TODO_MD>>/$TODO_MD}"
  TODO_CONTENT="$(cat "$TODO_MD")"
  PROMPT="${PROMPT//<<TODO_CONTENT>>/$TODO_CONTENT}"

  {
    echo "------ [iter $iter] prompt begin ------"
    echo "$PROMPT"
    echo "------ [iter $iter] prompt end   ------"
    echo "[iter $iter] running codex..."
  } > "$log"

  # --- Codex 実行(リアルタイム表示: STREAM=onでtee)
  set +e  # codex exec のエラーを許容
  if [[ "${STREAM}" == "on" ]]; then
    if command -v timeout >/dev/null 2>&1; then
      # pipefailだと左の失敗が伝播するので末尾に || true を付ける
      timeout "$TIMEOUT_SECS" codex exec "$PROMPT" 2>&1 | tee -a "$log" || true
    else
      codex exec "$PROMPT" 2>&1 | tee -a "$log" || true
    fi
  else
    if command -v timeout >/dev/null 2>&1; then
      timeout "$TIMEOUT_SECS" codex exec "$PROMPT" >> "$log" 2>&1 || true
    else
      codex exec "$PROMPT" >> "$log" 2>&1 || true
    fi
  fi
  set -e  # エラーチェックを再開

  sleep "$PAUSE_SEC"

  after_unchecked="$(unchecked_count)"
  echo "[iter $iter] unchecked: $before_unchecked -> $after_unchecked"
  echo "[iter $iter] unchecked: $before_unchecked -> $after_unchecked" >> "$log"

  if $COMMIT && command -v git >/dev/null 2>&1; then
    git add -A || true
    if ! git diff --cached --quiet; then
      git commit -m "codex: iter $iter (unchecked $before_unchecked -> $after_unchecked)" >/dev/null 2>&1 || true
    fi
  fi

  if ! progress_made "$before_unchecked"; then
    echo "[iter $iter] no progress; stop."
    echo "[iter $iter] no progress; stop." >> "$log"
    break
  fi
done

stop_heartbeat

# --- finish ---
if has_unchecked; then
  left="$(unchecked_count)"
  echo "[done] finished with $left unchecked remaining."
  notify_win "ToDo 実行を終了(未完了 $left 件)"
else
  echo "[done] all tasks completed."
  notify_win "All tasks in $(basename "$TODO_MD") are completed ✅"
fi

カスタマイズ

細部は Codex などにカスタマイズ依頼いただけると!

Discussion