“Log in プレーリーカードTech Blog 目次 Claude Codeを24時間動かす技術 umihico 2025/08/18に公開 AI Python RSpec Claude Claude Code Tech Claude Codeを24時間動かす技術 きっかけ・背景・課題 リファクタという作業自体はClaude CodeをはじめAIが得意とする作業ですが、対象ファイル数が数百あると、通常のClaude Codeの実行では、作業が途中で停止してしまうという問題がありました。その問題を解決するため、tmuxとPythonを組み合わせて、セッションを永続化し、停止したら自動再起動するスクリプトを書きました。今回、Railsで動くプレーリーカードのRspecの大規模なリファクタリングを行おうと思いました。リファクタの内容は現在コントローラー1ファイルに対してテスト1ファイルにしていますが、これをアクションごとにテスト側のファイルを分割する作業です。 実装のポイント tmuxセッションでClaude Codeを実行 Pythonスクリプトで出力を監視し、変更がなければ自動再起動 タスクリストから順次処理し、処理済みタスクを自動削除 全体アーキテクチャ clauderというコマンドを自作して、以下の流れで動作します: ユーザー が clauder コマンドを実行 .zshrc内のclauder関数 が SESSION_NAME と COMMAND_FILE_PATH を受け取って Pythonスクリプトを起動 .repeat_tmux.py(監視スクリプト) が以下を実行: tmuxセッションの管理 5秒ごとの出力差分監視 変更がない場合の自動再起動 tmuxセッション内でClaude Code が実行され: order.txtの指示を読み取り tasks.txtから1行目のファイルパスを取得 RSpecファイルの分割処理を実行 ファイルシステムで管理されるデータ: tasks.txt: 処理対象ファイルのリスト order.txt: AIへの処理指示書 spec/requests/: 分割対象のRSpecファイル群 実装の詳細 1. .zshrc – clauder関数の定義 function clauder() { local SESSION_NAME=$1 local COMMAND_FILE_PATH=$2 python ~/.repeat_tmux.py $SESSION_NAME $COMMAND_FILE_PATH } シンプルなラッパー関数として定義し、Pythonスクリプトを呼び出します。 2. .repeat_tmux.py – 監視と自動再起動の実装 後ほど要点を解説しますが先にファイルの中身を紹介します。 #!/usr/bin/env python3 import subprocess import time import os import tempfile import difflib import argparse def kill_tmux_session(session_name): “””指定されたtmuxセッションをkillする””” try: # 指定されたセッションが存在するかチェック result = subprocess.run([“tmux”, “has-session”, “-t”, session_name], capture_output=True, text=True) if result.returncode == 0: # セッションが存在する場合のみkill subprocess.run([“tmux”, “kill-session”, “-t”, session_name], check=True) print(f”tmuxセッション ‘{session_name}’ をkillしました”) else: print(f”tmuxセッション ‘{session_name}’ は存在しません”) except subprocess.CalledProcessError as e: print(f”tmuxセッション ‘{session_name}’ のkill中にエラーが発生しました: {e}”) except Exception as e: print(f”予期しないエラーが発生しました: {e}”) def start_new_tmux_session(session_name, command_file_path): “””新しいtmuxセッションを起動し、指定されたファイルの内容をclaudeコマンドで実行する””” try: # ファイルの内容を読み込み message = f”{command_file_path}を読み込んで、指示を実行してください。” # 新しいtmuxセッションを起動し、claudeコマンドを実行 claude_command = f’claude “{message}”‘ subprocess.run([“tmux”, “new-session”, “-d”, “-s”, session_name, claude_command], check=True) time.sleep(10) # 起動に時間もかかり差分なしの判定防止 print( f”新しいtmuxセッション ‘{session_name}’ を起動し、claudeコマンドを実行しました: {claude_command}”) except FileNotFoundError: print(f”ファイル ‘{command_file_path}’ が見つかりません”) raise except Exception as e: print(f”tmuxセッション ‘{session_name}’ の起動中にエラーが発生しました: {e}”) raise def capture_tmux_pane(session_name): “””指定されたtmuxセッションのペインの内容をキャプチャして返す””” try: result = subprocess.run( [“tmux”, “capture-pane”, “-t”, f”{session_name}:0.0″, “-p”], capture_output=True, text=True ) return result.stdout except Exception as e: print(f”ペインのキャプチャ中にエラーが発生しました: {e}”) return None def get_diff_content(previous_content, current_content): “””前回の内容と現在の内容の差分を取得し、行数も返す””” try: if previous_content is None: return “初回実行のため差分なし”, 0 # 前回の内容と現在の内容を行に分割 previous_lines = previous_content.splitlines(keepends=True) current_lines = current_content.splitlines(keepends=True) # difflibを使用して差分を生成 diff = difflib.unified_diff( previous_lines, current_lines, fromfile=’previous’, tofile=’current’, lineterm=” ) diff = [ line for line in diff if line.startswith(‘+’) or line.startswith(‘-‘) if not line.startswith(‘+++’) and not line.startswith(‘—‘) ] diff_content = ‘n’.join(diff) diff_lines = diff_content.split(‘n’) # 行数を計算 line_count = len(diff_lines) return diff_content, line_count except Exception as e: print(f”差分の取得中にエラーが発生しました: {e}”) return None, 0 WAIT_SEC = 5 def main(): # argparseでセッション名とファイルパスを受け取る parser = argparse.ArgumentParser( description=’指定されたtmuxセッションを監視し、claudeコマンドを実行します。実行例: clauder ‘) parser.add_argument(‘session_name’, help=’tmuxセッション名’) parser.add_argument(‘command_file_path’, help=’claudeに送信するメッセージが含まれるファイルのパス’) args = parser.parse_args() # 指定されたtmuxセッションをkillして新しく起動 kill_tmux_session(args.session_name) start_new_tmux_session(args.session_name, args.command_file_path) previous_content = None print(f”tmuxセッション ‘{args.session_name}’ の監視を開始…”) while True: try: # tmuxペインの内容をキャプチャ current_content = capture_tmux_pane(args.session_name) if current_content is None: print(“ペインのキャプチャに失敗しました”) time.sleep(WAIT_SEC) continue # 前回の内容と比較 if previous_content is not None and current_content == previous_content: print( f”変更が検出されませんでした {time.strftime(‘%Y-%m-%d %H:%M:%S’)}”) # 新しいtmuxセッションを起動 kill_tmux_session(args.session_name) start_new_tmux_session( args.session_name, args.command_file_path) else: print( f”変更が検出されました {time.strftime(‘%Y-%m-%d %H:%M:%S’)}”) # 差分を取得 diff_content, line_count = get_diff_content( previous_content, current_content) if diff_content: print(f”差分行数: {line_count}”) if line_count > 0: print(“差分内容:”) print(diff_content) else: print(“差分が利用できません”) previous_content = current_content # 5秒待機 time.sleep(WAIT_SEC) except KeyboardInterrupt: print(“nユーザーによって監視が停止されました”) break except Exception as e: print(f”予期しないエラー: {e}”) time.sleep(WAIT_SEC) if __name__ == “__main__”: main() 3. order.txt – AIへの処理指示書 参考として今回用いたorder.txtも記載しておきます。こちらもAIと壁打ちして生成させています。使途に応じた今まで通りのプロンプトにタスク管理にファイルシステムを使うイメージです。 # RSpecファイルのアクション別分割指示 あなたは RSpec ファイルをアクションごとに分割する作業を担当します。 ## 作業手順 1. **tasks.txt から1行目のファイルパスを取得** “`bash TARGET_FILE=$(head -n 1 tasks.txt) echo “処理対象: $TARGET_FILE” tasks.txt から処理済みファイルを削除 tail -n +2 tasks.txt > tasks_tmp.txt && mv tasks_tmp.txt tasks.txt 対象ファイルの詳細分析 ファイル内容を読み取り 含まれるアクション(index, show, create, update, destroy, new, edit等)を特定 各アクションのテストケース数をカウント 分割前の動作確認 # 分割前のテスト実行(パス数と件数を記録) docker compose exec web bundle exec rspec $TARGET_FILE –format progress 実行結果(パス数、失敗数、総件数)をメモ アクション別ファイル分割実行 例:spec/requests/users_spec.rb → spec/requests/users/index_spec.rb spec/requests/users/show_spec.rb spec/requests/users/create_spec.rb spec/requests/users/update_spec.rb spec/requests/users/destroy_spec.rb 分割後の動作確認 # 各分割ファイルのテスト実行 for file in spec/requests/users/*_spec.rb; do echo “Testing: $file” docker compose exec web bundle exec rspec “$file” –format progress done 分割前後でテスト件数が一致することを確認 全てのテストがパスすることを確認 元ファイルの削除 分割完了後、元のファイルを削除 rubocop & コミット docker compose exec web bundle exec rubocop -A “[FILEPATH]” git add [FILEPATH] (削除した方もgit add忘れないで) git commit -m “test: [FILEPATH]をアクション毎によりファイル分割” 分割結果の報告 分割されたファイル一覧 テスト件数の確認結果 動作確認の結果 注意事項 必ず docs/rspec.md の方針に従って分割してください 分割前後でテスト件数が変わらないことを確認してください コメントも必ず移行してください 全てのテストがパスすることを確認してください 日本語でのテスト記述を保持してください ファイル名は {controller名}/{action名}_spec.rb の形式にしてください エラー対応 テスト失敗がある場合は、原因を調査して修正 テスト件数が合わない場合は、分割ロジックを見直し ファイルが見つからない場合は、tasks.txt の更新状況を確認 完了条件 元ファイルが正常にアクション別に分割されている 分割前後でテスト件数が一致している 全てのテストが正常にパスしている tasks.txt から処理済みファイルが削除されている どうしてもrspecが解決できない移行不可ファイルが見つかったら、該当のファイルは回復、新ファイルは削除して、tasks.errors.txtに追記してtasks.txtからは除去しておいてください。 1つのファイルの分割が完了したら作業終了。次のファイルは別のAIインスタンスが処理します。 ### 4. tasks.txt – 処理対象ファイルリスト 今回はファイル名ですが、URLであったりタスクに応じた自由な文字列のリストでも勿論可です。 spec/requests/users_spec.rb spec/requests/cards_spec.rb …中略… ## クイックスタート / 最短手順 ### 1. 必要なファイルの準備 “`bash # .zshrcに関数を追加 echo ‘function clauder() { local SESSION_NAME=$1 local COMMAND_FILE_PATH=$2 python ~/.repeat_tmux.py $SESSION_NAME $COMMAND_FILE_PATH }’ >> ~/.zshrc # Pythonスクリプトを配置 cp .repeat_tmux.py ~/ # 処理対象ファイルリストを作成 AIに抽出させます # 指示書を作成(order.txtの内容をコピー) 2. 実行 session名を明示することでtmuxを他の用途(別件のclauderなど)でも使えるようにしています。 clauder split-rspec order.txt 実装の要点 監視メカニズムの実装 5秒毎にClaude Codeのセッションをキャプチャして秒数、トークン数など含め描画の更新が一切ストップしていたら作業が停止した見なし再起動します。 # 5秒ごとに出力をチェック if previous_content is not None and current_content == previous_content: print(f”変更が検出されませんでした {time.strftime(‘%Y-%m-%d %H:%M:%S’)}”) # 変更がない場合は自動再起動 kill_tmux_session(args.session_name) start_new_tmux_session(args.session_name, args.command_file_path) このシンプルな仕組みにより、Claude Codeが停止した場合でも自動的に再起動されます。 タスクキューの実装 # 1行目を取得して処理 TARGET_FILE=$(head -n 1 tasks.txt) # 処理後に1行目を削除 tail -n +2 tasks.txt > tasks_tmp.txt && mv tasks_tmp.txt tasks.txt 指示の途中変更 処理が開始すると、初期のorder.txtがもたらした実行結果への不満が多分あるかと思います。その際は実行中でも後述するorder.txtを修正したりgitコマンドで編集を取り消してtasks.txtに消えたファイル名を積んで再タスク化などすることで次からは改善されたプロンプトで実行が継続します。 まとめ Claude Codeを24時間稼働させる仕組みを構築することで、大規模なコードリファクタリングを自動化できました。ちなみにPythonスクリプト、order.txt、tasks.txt、本記事の大半も全てClaude Codeに生成させています。 umihico プレーリーカードのエンジニアです フォロー プレーリーカードTech Blog Publication フォロー Discussion ログインするとコメントできます Login エンジニアのための情報共有コミュニティ About Zennについて 運営会社 お知らせ・リリース イベント Guides 使い方 法人向けメニュー New Publication / Pro よくある質問 Links X(Twitter) GitHub メディアキット Legal 利用規約 プライバシーポリシー 特商法表記 ” https://zenn.dev/studio_prairie/articles/0c0cc762996079#:~:text=Log%20in,%E7%89%B9%E5%95%86%E6%B3%95%E8%A1%A8%E8%A8%98
Claude Codeを24時間動かす技術
