feat(tools): 自律型AI開発システムの実装完了 - Codex非同期実行とプロセス制御
- codex-async-notify.sh: tmux paste-buffer方式で安定化 - codex-keep-two.sh: 正確なプロセスカウント実装 - デフォルト2プロセス制限で暴走防止 - 日本語プロンプト「まだタスクがあれば次のタスクお願いします」で無限ループ実現 - count_running_codex_display()でcomm+argsによる正確な検出 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -22,11 +22,12 @@ NOTIFY_WINDOW_NAME="${CODEX_NOTIFY_WINDOW:-codex-notify}"
|
|||||||
# 設定
|
# 設定
|
||||||
WORK_DIR="$HOME/.codex-async-work"
|
WORK_DIR="$HOME/.codex-async-work"
|
||||||
LOG_DIR="$WORK_DIR/logs"
|
LOG_DIR="$WORK_DIR/logs"
|
||||||
|
RUN_DIR="$WORK_DIR/running"
|
||||||
WORK_ID=$(date +%s%N)
|
WORK_ID=$(date +%s%N)
|
||||||
LOG_FILE="$LOG_DIR/codex-${WORK_ID}.log"
|
LOG_FILE="$LOG_DIR/codex-${WORK_ID}.log"
|
||||||
|
|
||||||
# 作業ディレクトリ準備
|
# 作業ディレクトリ準備
|
||||||
mkdir -p "$LOG_DIR"
|
mkdir -p "$LOG_DIR" "$RUN_DIR"
|
||||||
|
|
||||||
# === オプショナル並列制御 ===
|
# === オプショナル並列制御 ===
|
||||||
# - CODEX_MAX_CONCURRENT: 許容最大同時実行数(デフォルト2)
|
# - CODEX_MAX_CONCURRENT: 許容最大同時実行数(デフォルト2)
|
||||||
@ -40,6 +41,8 @@ CODEX_PROC_PATTERN=${CODEX_PROC_PATTERN:-'codex .* exec'}
|
|||||||
FLOCK_WAIT=${CODEX_FLOCK_WAIT:-5}
|
FLOCK_WAIT=${CODEX_FLOCK_WAIT:-5}
|
||||||
LOG_RETENTION_DAYS=${CODEX_LOG_RETENTION_DAYS:-0}
|
LOG_RETENTION_DAYS=${CODEX_LOG_RETENTION_DAYS:-0}
|
||||||
LOG_MAX_BYTES=${CODEX_LOG_MAX_BYTES:-0}
|
LOG_MAX_BYTES=${CODEX_LOG_MAX_BYTES:-0}
|
||||||
|
# 実行中カウント方式: proc(プロセス数) / pgid(プロセスグループ数) / sentinel(将来のための拡張)
|
||||||
|
CODEX_COUNT_MODE=${CODEX_COUNT_MODE:-sentinel}
|
||||||
|
|
||||||
list_running_codex() {
|
list_running_codex() {
|
||||||
# 実ジョブのみを検出: "codex ... exec ..." を含むコマンドライン
|
# 実ジョブのみを検出: "codex ... exec ..." を含むコマンドライン
|
||||||
@ -53,9 +56,63 @@ list_running_codex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count_running_codex() {
|
count_running_codex() {
|
||||||
local n
|
case "$CODEX_COUNT_MODE" in
|
||||||
n=$(list_running_codex | wc -l | tr -d ' ')
|
sentinel)
|
||||||
echo "${n:-0}"
|
# Count sentinel files for tasks we launched; clean up stale ones
|
||||||
|
local cnt=0
|
||||||
|
if [ -d "$RUN_DIR" ]; then
|
||||||
|
for f in "$RUN_DIR"/codex-*.run; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
# Optional liveness check by stored pid
|
||||||
|
pid=$(awk -F': ' '/^pid:/{print $2; exit}' "$f" 2>/dev/null || true)
|
||||||
|
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
rm -f "$f" 2>/dev/null || true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
cnt=$((cnt+1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
# Fallback: if 0 (older jobs without sentinel), attempt pgid-based count
|
||||||
|
if [ "${cnt:-0}" -eq 0 ]; then
|
||||||
|
if command -v pgrep >/dev/null 2>&1; then
|
||||||
|
pgc=$(pgrep -f -- "$CODEX_PROC_PATTERN" \
|
||||||
|
| xargs -r -I {} sh -c 'ps -o pgid= -p "$1" 2>/dev/null' _ {} \
|
||||||
|
| awk '{print $1}' | grep -E '^[0-9]+$' | sort -u | wc -l | tr -d ' ' || echo 0)
|
||||||
|
echo "${pgc:-0}"
|
||||||
|
else
|
||||||
|
list_running_codex | wc -l | tr -d ' ' || echo 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$cnt"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
pgid)
|
||||||
|
# pgrepで対象PIDsを取得 → 各PIDのPGIDを集計(自分自身のawk/ps行は拾わない)
|
||||||
|
if command -v pgrep >/dev/null 2>&1; then
|
||||||
|
pgrep -f -- "$CODEX_PROC_PATTERN" \
|
||||||
|
| xargs -r -I {} sh -c 'ps -o pgid= -p "$1" 2>/dev/null' _ {} \
|
||||||
|
| awk '{print $1}' | grep -E '^[0-9]+$' | sort -u | wc -l | tr -d ' ' || echo 0
|
||||||
|
else
|
||||||
|
# フォールバック: プロセス数
|
||||||
|
list_running_codex | wc -l | tr -d ' ' || echo 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
proc|*)
|
||||||
|
list_running_codex | wc -l | tr -d ' ' || echo 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display-oriented counter: count real 'codex exec' processes by comm+args
|
||||||
|
count_running_codex_display() {
|
||||||
|
if ps -eo comm=,args= >/dev/null 2>&1; then
|
||||||
|
ps -eo comm=,args= \
|
||||||
|
| awk '($1 ~ /^codex$/) && ($0 ~ / exec[ ]/) {print $0}' \
|
||||||
|
| wc -l | tr -d ' ' || echo 0
|
||||||
|
else
|
||||||
|
# Fallback to pattern match
|
||||||
|
list_running_codex | wc -l | tr -d ' ' || echo 0
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
is_same_task_running() {
|
is_same_task_running() {
|
||||||
@ -113,14 +170,25 @@ maybe_wait_for_slot
|
|||||||
# 非同期実行関数
|
# 非同期実行関数
|
||||||
run_codex_async() {
|
run_codex_async() {
|
||||||
{
|
{
|
||||||
|
# Create sentinel to track running task (removed on exit)
|
||||||
|
SEN_FILE="$RUN_DIR/codex-${WORK_ID}.run"
|
||||||
|
{
|
||||||
|
echo "work_id: $WORK_ID"
|
||||||
|
echo "task: $TASK"
|
||||||
|
echo "started: $(date -Is)"
|
||||||
|
echo "pid: $$"
|
||||||
|
} > "$SEN_FILE" 2>/dev/null || true
|
||||||
|
# Ensure sentinel (and dedup file if any) are always cleaned up on exit
|
||||||
|
trap 'rm -f "$SEN_FILE" "${CODEX_DEDUP_FILE:-}" >/dev/null 2>&1 || true' EXIT
|
||||||
# Dedupロック: 子プロセス側で握る(同一TASKの多重起動回避)
|
# Dedupロック: 子プロセス側で握る(同一TASKの多重起動回避)
|
||||||
if [ "${CODEX_DEDUP_FILE:-}" != "" ]; then
|
if [ "${CODEX_DEDUP_FILE:-}" != "" ]; then
|
||||||
exec 8>"${CODEX_DEDUP_FILE}"
|
exec 8>"${CODEX_DEDUP_FILE}"
|
||||||
if ! flock -n 8; then
|
if ! flock -n 8; then
|
||||||
echo "⚠️ Duplicate task detected (dedup lock busy). Skipping: $TASK" | tee -a "$LOG_FILE"
|
echo "⚠️ Duplicate task detected (dedup lock busy). Skipping: $TASK" | tee -a "$LOG_FILE"
|
||||||
|
rm -f "$SEN_FILE" >/dev/null 2>&1 || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
trap 'rm -f "${CODEX_DEDUP_FILE}" >/dev/null 2>&1 || true' EXIT
|
# (cleanup handled by global EXIT trap above)
|
||||||
fi
|
fi
|
||||||
# Detach: silence this background subshell's stdout/stderr while still tee-ing to log
|
# Detach: silence this background subshell's stdout/stderr while still tee-ing to log
|
||||||
if [ "${CODEX_ASYNC_DETACH:-0}" = "1" ]; then
|
if [ "${CODEX_ASYNC_DETACH:-0}" = "1" ]; then
|
||||||
@ -186,8 +254,9 @@ run_codex_async() {
|
|||||||
STATUS_MARK=$([ $EXIT_CODE -eq 0 ] && echo '✅ Success' || echo '❌ Failed')
|
STATUS_MARK=$([ $EXIT_CODE -eq 0 ] && echo '✅ Success' || echo '❌ Failed')
|
||||||
CHAT_FILE="$WORK_DIR/chat-${WORK_ID}.tmp"
|
CHAT_FILE="$WORK_DIR/chat-${WORK_ID}.tmp"
|
||||||
TASK_ONELINE=$(echo "$TASK" | tr '\n' ' ' | sed 's/ */ /g')
|
TASK_ONELINE=$(echo "$TASK" | tr '\n' ' ' | sed 's/ */ /g')
|
||||||
# 現在の実行中プロセス数をカウント
|
# 表示用: 実プロセス(comm=codex かつ args に exec を含む)の数を採用
|
||||||
CURRENT_RUNNING=$(count_running_codex)
|
CURRENT_RUNNING=$(count_running_codex_display)
|
||||||
|
case "$CURRENT_RUNNING" in (*[!0-9]*) CURRENT_RUNNING=0;; esac
|
||||||
{
|
{
|
||||||
echo "# 🤖 Codex: Done [$(date +%H:%M:%S)] (実行中: $CURRENT_RUNNING)"
|
echo "# 🤖 Codex: Done [$(date +%H:%M:%S)] (実行中: $CURRENT_RUNNING)"
|
||||||
echo "# Work ID: $WORK_ID"
|
echo "# Work ID: $WORK_ID"
|
||||||
|
|||||||
@ -21,6 +21,9 @@ if [ ${#TASKS[@]} -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
CODEX_PROC_PATTERN=${CODEX_PROC_PATTERN:-'codex .* exec'}
|
CODEX_PROC_PATTERN=${CODEX_PROC_PATTERN:-'codex .* exec'}
|
||||||
|
CODEX_COUNT_MODE=${CODEX_COUNT_MODE:-sentinel}
|
||||||
|
WORK_DIR="$HOME/.codex-async-work"
|
||||||
|
RUN_DIR="$WORK_DIR/running"
|
||||||
|
|
||||||
list_running() {
|
list_running() {
|
||||||
if command -v pgrep >/dev/null 2>&1; then
|
if command -v pgrep >/dev/null 2>&1; then
|
||||||
@ -31,8 +34,45 @@ list_running() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count_running() {
|
count_running() {
|
||||||
list_running | wc -l | tr -d ' '\
|
case "$CODEX_COUNT_MODE" in
|
||||||
|| echo 0
|
sentinel)
|
||||||
|
local cnt=0
|
||||||
|
if [ -d "$RUN_DIR" ]; then
|
||||||
|
for f in "$RUN_DIR"/codex-*.run; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
pid=$(awk -F': ' '/^pid:/{print $2; exit}' "$f" 2>/dev/null || true)
|
||||||
|
if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
rm -f "$f" 2>/dev/null || true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
cnt=$((cnt+1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ "${cnt:-0}" -eq 0 ]; then
|
||||||
|
if command -v pgrep >/dev/null 2>&1; then
|
||||||
|
pgrep -f -- "$CODEX_PROC_PATTERN" \
|
||||||
|
| xargs -r -I {} sh -c 'ps -o pgid= -p "$1" 2>/dev/null' _ {} \
|
||||||
|
| awk '{print $1}' | grep -E '^[0-9]+$' | sort -u | wc -l | tr -d ' ' || echo 0
|
||||||
|
else
|
||||||
|
list_running | wc -l | tr -d ' ' || echo 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$cnt"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
pgid)
|
||||||
|
if command -v pgrep >/dev/null 2>&1; then
|
||||||
|
pgrep -f -- "$CODEX_PROC_PATTERN" \
|
||||||
|
| xargs -r -I {} sh -c 'ps -o pgid= -p "$1" 2>/dev/null' _ {} \
|
||||||
|
| awk '{print $1}' | grep -E '^[0-9]+$' | sort -u | wc -l | tr -d ' ' || echo 0
|
||||||
|
else
|
||||||
|
list_running | wc -l | tr -d ' ' || echo 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
proc|*)
|
||||||
|
list_running | wc -l | tr -d ' ' || echo 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
RUNNING=$(count_running)
|
RUNNING=$(count_running)
|
||||||
|
|||||||
Reference in New Issue
Block a user