消したはずの claude -p が残っていた ── C3 v2.6.1〜v2.7.0
前回記事:
https://zenn.dev/satoh_y_0323/articles/db51bb73a97c19
C3 GitHub:https://github.com/satoh-y-0323/claude-code-conductor/PyPI:https://pypi.org/project/claude-code-conductor//公式ドキュメント:https://satoh-y-0323.github.io/claude-code-conductor/
本記事のスコープ:v2.6.1 の定期 security-audit フィックスと、v2.7.0 で claude -p を完全除去した話。おまけで permission_handler の使い勝手改善も紹介します。
はじめに
前回の記事(v2.5.0〜v2.6.0)では、Codex CLI との並列レビューで「Claude が見逃した本物の脆弱性を Codex が見つけた」という話をしました。
今回は少し趣が違います。
C3 は v2.5.0 で Parallel Orchestra(PO)という別プロセス方式を廃止し、
claude -p(ヘッドレス起動)をほぼ使わない構成に移行していました。「これで claude -p は消えた」と思っていたところ、Stop hook の深いところにひっそりと生き残っていた ── そういう話です。
さらに「Agent として実装し直してほしい」と依頼したら、Claude が Skill として実装してしまったというオチもあります。
v2.6.1: 定期 security-audit の結果を適用
v2.7.0 の話に入る前に、直前リリースを軽く紹介します。
v2.6.1 は「API 変更なし・定期 security-audit の結果をフィックスするだけ」のパッチリリースです。
セキュリティ修正 3 件
[SR-INJ-002] PowerShell の EncodedCommand 化
前回 Codex が指摘した PowerShell インジェクション対策の延長です。Windows 通知を送る箇所で
safe_msgを f-string に直接埋め込んでいた方式を廃止し、
base64.b64encodeで変換してから
-EncodedCommandに渡すよう変更しました。
'・バッククォート・
$を含むメッセージでもインジェクション不可能になっています。
[SR-AI-001] LLM 子プロセスの攻撃面縮小
consolidate_memory.pyが
claude -pでサブプロセスを起動する箇所で、
--dangerously-skip-permissionsフラグを除去し
--tools ""で全ツール無効化しました。この時点ではまだ
claude -p自体は残っていたわけですが、権限は最小化しています。
[SR-V-001] prompt-history の秘密情報マスク
select_tier.pyがプロンプト先頭 200 文字を
.claude/logs/prompt-history.jsonlに保存する際、API キー・トークン・パスワード相当の 7 パターンを
***でマスクするようにしました。
コード品質修正 14 件
sqlite3.connectを直接書いていた箇所の
_BUSY_TIMEOUT_MS統一、アトミック書き込み漏れの補完、型分岐の明示化など。いずれも security-audit エージェントによる定期スキャンで検出したものです。
v2.7.0 メインストーリー: 消したはずの claude -p が残っていた
背景: PO 廃止で消したつもりだった
C3 は v2.1.0〜v2.5.0 の 4 日間 5 リリースで Parallel Orchestra(PO)を完全廃止しました。PO は別プロセスでエージェントを起動する方式で、中心的な実装が
claude -pの呼び出しでした。
廃止後は Claude Code のインタラクティブセッション内で Agent ツールを使う方式に完全移行。
claude -pの出番はなくなったはずでした。
Stop hook に潜んでいた残骸
ところが、セッション終了時に動く Stop hook の中に、まだ生きている
claude -pがありました。
.claude/hooks/consolidate_memory.pyは、セッション終了時に過去 7 日分のセッションファイルを集約して
llm_summary.mdを更新する処理を担っています。この「集約」の部分が LLM を呼ぶ必要があり、その実装として
claude -pでサブプロセスを起動していたのです。
PO を廃止したとき、この hook は「エージェントオーケストレーションではなく単純な LLM 呼び出し」として扱われていたため、見落とされました。
# consolidate_memory.py の旧実装(簡略)
def run_llm_summary(prompt: str) -> str:
result = subprocess.run(
["claude", "-p", prompt, "--dangerously-skip-permissions"],
capture_output=True, text=True
)
return result.stdout
Agent として実装してほしいと依頼したら、Skill で実装された
「Stop hook から
claude -pを廃止して、代わりに Agent として LLM 要約を実行するよう実装してほしい」と依頼しました。
Claude が Wave 0 で実装したのは .claude/skills/summarize-memory/SKILL.md でした。
Skill として実装されてしまったわけです。
一見それっぽいのですが、Skill は動きません。Skill ツールは LLM のコンテキスト内でマークダウンを読み込む仕組みで、子プロセスを起動しません。Stop hook は Python スクリプトとして Claude Code と別プロセスで動くため、「Skill を呼ぶ」という概念が成立しないのです。
Stop hook (Python サブプロセス) ↓ 呼べない Skill ツール(Claude Code セッション内の LLM 読み込み)
作り直し: exit 2 + Agent(run_in_background=True)
正しい実装は、Stop hook が Claude Code 本体に対して「Agent を起動してくれ」と指示を渡すことです。
Claude Code の hook 仕様には
exit 2+
stderrで LLM へフィードバックを返す機能があります。これを使うと、hook が終了した後に Claude が stderr の内容をシステムメッセージとして受け取り、次のアクションを取ります。
# session_stop.py の新実装(簡略)
def main():
if not _needs_summary():
sys.exit(0)
instruction = """
summarize-memory agent を背景で起動してください:
Agent(subagent_type="summarize-memory", run_in_background=True, ...)
"""
print(instruction, file=sys.stderr)
sys.exit(2) # exit 2 で Claude へフィードバック
そして
.claude/agents/summarize-memory.mdを新規作成し、LLM 要約の手順を Agent 定義として移植しました(旧 SKILL.md の内容がそのまま使えます)。
Stop hook (Python) → exit 2 + stderr ↓ Claude Code が受け取る Claude → Agent(run_in_background=True, subagent_type="summarize-memory") を起動 ↓ 非同期実行 summarize-memory agent が llm_summary.md を更新
この方式のメリットは、Stop hook がブロッキングなしで終了できることです。旧実装は
claude -pの完了を待ってから hook が返っていたため、セッション終了のたびに数秒〜十数秒の待機が発生していました。新実装では hook は即座に終了し、要約は background で実行されます。
要約の必要性を機械的に判定
あわせて、毎回 LLM を呼ばないための仕組みも整備しました。
旧実装はクールダウン(60 分)でスロットリングしていましたが、「60 分以内に 2 回セッションを終了したら要約が更新されない」という問題があります。
新実装では
os.path.getmtime()でファイルの更新時刻を比較します。
def _needs_summary() -> bool:
"""session ファイルが llm_summary.md より新しければ要約が必要"""
summary_mtime = _safe_mtime(SUMMARY_PATH)
session_mtimes = [_safe_mtime(p) for p in SESSION_DIR.glob("*.tmp")]
if not session_mtimes:
return False
return max(session_mtimes) > summary_mtime
「セッションファイルが更新されていれば要約が必要、そうでなければ不要」という判断を LLM なしで行えます。クールダウンという恣意的な時間制限が消えて、シンプルになりました。
結果: C3 から claude -p が消えた
この変更で
consolidate_memory.pyの LLM 関連関数・定数 11 個が削除され、ファイルが -400 行以上スリムになりました。C3 の全コードベースから
claude -pの呼び出しが完全になくなっています。
その他の改善: permission_handler の使い勝手向上
ボタン付きトースト通知でワンクリック auto_allow
Claude Code が「このコマンドを実行してもいいですか?」と権限確認ダイアログを出す場面があります。毎回承認するコマンドがあれば
permission_rules.jsonの
auto_allowに追加しておくと確認がスキップされます。
これまでは
permission_rules.jsonを手で開いてパターンを書き込む必要がありました。
Bash(git status*)のようなワイルドカードパターンを自分で考えて書くのは地味に面倒です。
v2.7.0 からは Windows 通知に 「自動承認に追加: Bash(git status *)」ボタンが付くようになりました。
[権限確認] Bash: git status --short
[承認] [拒否] [自動承認に追加: Bash(git status*)]
↑ クリックで permission_rules.json に追記
permission_handler.pyがツール名と引数からワイルドカードパターンを自動推定します。
| ツール | 推定パターンの例 |
|---|---|
Bash |
コマンド先頭 1〜2 トークンにワイルドカード付加 |
Write/ Edit/ Read |
親ディレクトリ /** |
WebFetch |
domain:hostname |
ボタンクリックで
permission_rules.jsonへの書き込みはアトミック(mkstemp + os.replace)なので、並走する他のプロセスがファイルを読んでいても壊れません。
auto_allow リストに上限 100 件
auto_allowに際限なくパターンを追加できると、意図しないコマンドまで自動承認されるリスクが高まります。100 件を超えるとエラーになるサイズ制限を設けました。日常的な開発用途で 100 件を超えることはほぼないはずです。
worktree 並列実行時のファイル取り込み修正
parallel-agentsスキルで worktree を使った並列実行をすると、main ブランチへのファイル取り込み(merge)が一部ケースで失敗する問題がありました。
git check-ignore -qによる .gitignore チェックを入れた分岐ハイブリッド方式に変更し、構造的に修正しています。あわせて
worktree_guard.pyの CWD ベース自動有効化で、worktree 外への Write/Edit を
exit 2でブロックする保護が一貫して動くようになりました。
C3 が目指しているもの
ここで少し C3 の設計思想について話します。
C3 は「業務アプリケーション開発チームが使うヒューマンインザループ型の開発フレームワーク」を目指して作っています。
キーワードは 2 つです。
ヒューマンインザループ
C3 のワークフローには、各フェーズの間に必ず人間の確認ポイントがあります。
ヒアリング → [承認] → 設計 → [承認] → 計画 → [承認] → 実装(TDD wave)→ [承認] → レビュー → [承認] → 完了
「AI が自律的にどんどん動いてほしい」という用途には向いていません。各フェーズで AI が出した成果物を人間が確認・修正できる構造を意識的に維持しています。
これは趣味的な選択ではなく、業務での利用を考えたときの必然です。業務アプリケーションには「なぜこの設計にしたか」「このトレードオフをなぜ選んだか」という判断ポイントがあり、そこは AI に任せっきりにできません。レポートファイル(architecture-report / plan-report / code-review-report / security-review-report / test-report)を生成して人間が読み、判断してから次へ進む設計はそのためです。
自動化フレームワークではない
「C3 で CI/CD を自動化したい」「コードを書いて自動でデプロイしたい」という用途には、より適したツールがあります(OpenHands や AutoCodeRover など)。
C3 が得意なのは「開発サイクルの中で人間と AI が協調しながら品質を上げていく」場面です。
レポートの蓄積、パターンの学習、定期的な security-audit、過去の失敗アプローチの記録 ── こういった開発プロセスの知識管理を機械的にサポートするのが C3 の役割です。
バージョンまとめ
| バージョン | 日付 | 主な変更 |
|---|---|---|
| v2.6.1 | 2026-05-15 | security-audit 定期監査フィックス(セキュリティ 3 件・品質 14 件) |
| v2.7.0 | 2026-05-16 |
claude -p完全除去(Stop hook を background Agent 方式に移行)、permission_handler ボタン付き通知、auto_allow 上限、worktree 取り込み修正 |
おわりに
「消したはずだった
claude -p」は、定期的な security-audit と地道なコード棚卸しで発見できました。
Claude に「Agent として実装して」と依頼したら Skill で実装されてしまったのは、Skill と Agent の仕組みの違いを C3 自体が正確に伝えられていなかったということでもあります。SKILL.md と agents/*.md の使い分けを CLAUDE.md に明記したことで、再発は防げる見込みです。
こういった「AI が間違える場面を記録・修正していく」プロセス自体が、C3 を使った開発のサイクルになっています。
リンク
- C3 GitHub:https://github.com/satoh-y-0323/claude-code-conductor
- C3 PyPI:https://pypi.org/project/claude-code-conductor/
- C3 公式ドキュメント:https://satoh-y-0323.github.io/claude-code-conductor/
- 前回記事(Claude が見逃した脆弱性を Codex が発見した話、v2.5.0〜v2.6.0):https://zenn.dev/satoh_y_0323/articles/db51bb73a97c19