Claude Media
Claude Code -pモードでスクリプトやパイプラインを自動化する基本

Claude Code -pモードでスクリプトやパイプラインを自動化する基本

claude -pで非対話実行するときの基本コマンド、bareモードで変わること、ツール自動承認の使い分け、CIで止まる原因を実機の出力つきで解説します。

Claude Code -pモードは何を省いて何を残すか

claude -p(--printの短縮形)は、Claude Codeを対話画面なしで一度だけ実行するモードです。プロンプトを渡して結果を受け取り、そのまま終了します。CIパイプライン・pre-commitフック・cronジョブなど、人が画面の前にいない場所へ組み込むときの入口になります。Raspberry PiのGPIO制御スクリプトをcronで定期実行する場合も、同じ考え方です(Claude CodeでRaspberry PiのGPIOを制御する)。

ツール群・エージェントループ・文脈管理は対話セッションと共通です。省かれるのは、承認ダイアログや信頼確認のように人が答える画面です。CIで止まる原因の多くは、人が答えるはずだった問いに誰も答えなかったことで説明できます。この記事は、その問いを事前に潰す順番で進めます。

JSON出力の解析やストリーミングイベントの扱いはClaude Codeの構造化出力とストリーミングをツールに組み込むに譲ります。ここでは実行の土台だけを扱います。

基本の実行と終了コード

もっとも単純な形は、プロンプトを引数で渡す方法です。

claude -p "このプロジェクトが何をするか説明して"

標準入力からのパイプ渡しにも対応します。ビルドログを読ませて原因を説明させる例です。

cat build-error.txt | claude -p 'このビルドエラーの根本原因を簡潔に説明して' > output.txt

パイプで渡せる標準入力は10MBまでです。超えるとエラーと0以外の終了コードで止まるので、大きな入力はファイルに書き出し、プロンプトでパスを指します。

終了コードは成功が0、失敗が0以外です。無効なフラグはモデルを呼ぶ前に標準エラー出力へ出ますが、認証切れのような実行中の失敗は結果として標準出力に書き出されます。スクリプトで失敗を拾うときは、終了コードだけでなく標準出力の中身も見る必要があります。

v2.1.289で出るエラー出力

実行前に弾かれるエラーは、モデルを呼ばずに確かめられます。v2.1.289で出る出力です。

$ claude -p --bg "hi"
--bg and --print conflict: --print never starts the interactive session that `claude agents` attaches to, so the job would be unattachable. The prompt is the positional — drop --print: `claude --bg '<task>'`.
 
$ claude -p --bogus-flag "hi"
error: unknown option '--bogus-flag'
 
$ claude -p "hi" --permission-prompts maybe
error: option '--permission-prompts <target>' argument 'maybe' is invalid. Allowed choices are host, none.
 
$ claude -p </dev/null
Error: Input must be provided either through stdin or as a prompt argument when using --print

--bgとの競合は、-pが対話セッションを作らず、後からclaude agentsでアタッチできないという理由つきで止まります。使い分けは--bgと--printの競合エラーの解決法にまとめました。

もう1つ、標準入力が開いたまま何も流れてこない環境では、待ち時間が発生します。次の例では、3秒待ってから警告を出して先へ進みました。

$ claude -p --json-schema "not json" "hi"
Warning: no stdin data received in 3s, proceeding without it. If piping from a slow command, redirect stdin explicitly: < /dev/null to skip, or wait longer.
Error: --json-schema is not valid JSON: JSON Parse error: Unexpected identifier "not"

プロンプトを引数で渡す場合でも、標準入力が開いたままならこの3秒がかかりえます。警告文のとおり< /dev/nullを付ければ省けます。--json-schemaは、JSONとして読めない値なら上のエラーで終了します。JSONとして正しくてもJSON Schemaでない値は、Error: --json-schema is not a valid JSON Schemaで終了します。

権限モード — 既定は環境で変わる

-pにはツール承認に答える人がいません。確認が必要な操作は拒否されるので、最初に決めるのは、どの権限モードで走らせるかです。

-pの既定は常にManualとは限りません。権限モードの公式ページは、-pとAgent SDKの組み込みの既定を次のように分けています。

  • フィーチャーフラグを取得するセッションではdefault(Manual)
  • 取得しないセッション(サードパーティープロバイダーやテレメトリーをオフにした環境)では、v2.1.285以降はauto、それ以前はdefault
  • 組織のポリシーがautoの既定を止めている場合はdefault

同じコマンドでも、環境によって既定が食い違いえます。ジョブを再現可能にしたいなら、--permission-modeを毎回明示するのが確実です。

くらべる

権限を決める2つの方法

--allowedTools

個別に許可する

使うツールを名指しします。ReadとEditで読み書き、Bashでシェルコマンドが通ります。ただしautoで始まる実行では、裸のBashは広すぎる許可として外され、コマンドごとに分類器が審査します。

--permission-mode

基準を決める

セッション全体の基準を指定します。autoは分類器の審査、dontAskは確認になる操作の拒否、acceptEditsはファイル編集と、作業ディレクトリ内のmkdir・touch・rm・rmdir・mv・cp・sedの自動承認です。

2つは併用できます。ロックダウンしたCIなら、dontAskで基準を締め、必要なコマンドだけを--allowedToolsで足します。

claude -p "npm testを実行して" \
  --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"

dontAskは「事前承認したツールだけ」ではありません。作業ディレクトリのファイル読み取りや読み取り専用コマンドのように、Manualでも承認が要らない操作は動きます。拒否されるのは、本来なら確認が出る操作です。AskUserQuestionと、組織がaskに設定したコネクタのツールは、許可ルールに合致しても拒否されます。

テストを走らせて失敗を直す例は、個別許可の典型です。

claude -p "テストスイートを実行して失敗を直して" \
  --allowedTools "Bash,Read,Edit"

編集中心の作業なら、--permission-mode acceptEditsだけで足ります。

claude -p "lintの指摘を直して" --permission-mode acceptEdits

acceptEditsでも、読み取り専用コマンド以外のシェルコマンドは、--allowedToolsかpermissions.allowの許可が別に要ります。--dangerously-skip-permissionsは全部を通すフラグで、ヘルプはインターネットに出られないサンドボックスだけを推奨しています。権限モデルの設計の違いはAIコーディングエージェント権限モデル比較で掘り下げています。

誰も答えない前提なら --permission-prompts none

v2.1.259で--permission-promptsが加わりました。値はhostとnoneです。noneにすると、確認になる操作はPermissionRequestフックが許可しない限り自動で拒否され、Claudeには「承認できる人はいないので再試行しない」と伝わります。ホストのない-pでも拒否そのものは同じですが、この指定があるとClaudeに再試行しないよう伝わります。

claude -p "依存の固定版を更新してテストを回して" \
  --permission-mode auto --permission-prompts none

stream-jsonで出力しているなら、拒否はpermission_deniedというシステムメッセージで流れ、最終結果のpermission_denialsにも一覧が残ります。

--bareで何が変わるか

--bareは、スクリプトで「どのマシンでも同じ結果」を得るためのフラグです。ヘルプによると、設定やプラグインが定義するフック、LSP、プラグインの同期、自動メモリー、キーチェーンの読み取り、CLAUDE.mdの自動検出を省きます。Skillsは/skill-nameとしてなら引き続き呼べます。

くらべる

--bareの有無で変わること

既定

付けない

対話セッションと同じ文脈で始まり、認証はサブスクリプションのログインも使えます。

--bare

付ける

文脈は明示した分だけです。追加の指示は--append-system-prompt、設定は--settings、MCPは--mcp-configで渡します。

サブエージェントは--agents、プラグインは--plugin-dirです。Anthropic APIへの認証はANTHROPIC_API_KEYかapiKeyHelperだけです(Bedrockなどのクラウドプロバイダーは各自の認証情報を使います)。

v2.1.286以降は、--bareの制限が次のように徹底されています。

  • MCPサーバーは、コマンドラインで渡したものだけが接続されます
  • システムリマインダー(読んだファイルがディスク上で変わったことを知らせる通知など)がClaudeに届きません
  • バックグラウンドタスクは動かず、タイムアウトに達したコマンドは止まります

v2.1.285以前は一部しか効かず、--bareでも対話セッションがMCPサーバーを接続したり、システムリマインダーが付いたりしました。古いCLIが入ったランナーでは差が出ます。

公式ページは、--bareが将来-pの既定になると予告しています。それまでの間、--bareを付けない-pにはもう1つ注意があります。信頼確認が出ないため、初めて開いたフォルダーでも、プロジェクトの.claude/settings.jsonにあるフックが動き、.mcp.jsonのサーバーにも承認なしで接続します。その一方で、同じ設定ファイルのpermissions.allowとadditionalDirectoriesは、信頼されるまで使われません。標準エラー出力には「this workspace has not been trusted」の警告が出ます。--bareを付けると、リポジトリ側のフックやMCPサーバーは動きません。第三者のリポジトリを読ませるジョブでは、こちらが向いています。

認証はbareの有無で選び方が変わる

CIは対話ログインができないので、認証情報を環境変数で渡します。ANTHROPIC_API_KEYは、-pでは設定されていれば常に使われ、/loginのサブスクリプションより優先されます。シェルの設定ファイルに残ったキーが、Pro・Maxのつもりの実行をAPI課金に変える原因はここにあります。

claude setup-tokenが発行する1年有効のトークンはCLAUDE_CODE_OAUTH_TOKENとして渡しますが、--bareはこの変数を読みません。bareで走らせるジョブは、ANTHROPIC_API_KEYかapiKeyHelperを使います。

手順

CIジョブを組み立てる順序

  1. 1

    認証を決める

    --bareならAPIキー、付けないならAPIキーかsetup-tokenのトークンです。

  2. 2

    権限モードを明示する

    --permission-modeを毎回書き、ジョブの外の既定に頼りません。

  3. 3

    許可するコマンドを絞る

    --allowedTools "Bash(npm test)"のように、実行させるコマンドの形まで書きます。

  4. 4

    失敗を拾う

    終了コードと標準出力の両方を見ます。

スクリプトに組み込む実例

-pはlinterやレビューアとしてビルドスクリプトへ組み込めます。mainとの差分を渡してtypoを報告させるpackage.jsonの例です。

{
  "scripts": {
    "lint:claude": "git diff main | claude -p \"あなたはtypoリンターです。このdiffの各typoについてfilename:lineを1行、次の行に問題点を書いて。それ以外は何も返さないで\""
  }
}

差分をパイプで渡すため、diffを読むためのBash権限は要りません。エスケープした二重引用符のおかげでWindowsでも動きます。実行はnpm run lint:claudeです。数百〜数千ファイルへ同じ変更を機械的に当てたい場合は、-pをシェルのforループで回す方法があります。権限の絞り方とリトライ設計はClaude Codeをシェルループで回して複数ファイルを一括変更するにあります。

ステージした変更からコミットを作る例です。

claude -p "ステージした変更を見て適切なコミットを作成して" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"

許可ルールの末尾の *は前方一致です。*の前の半角スペースが肝で、省いたBash(git diff*)はgit diff-indexにも一致します。

ホスト型CIへの組み込みは、Claude CodeをGitHub Actionsに組み込むとClaude CodeをGitLab CI/CDに組み込むが詳しいです。cronやpre-commitのように自分でホストを管理する場合は、この記事のコマンドがそのまま使えます。

Skills・スラッシュコマンド・システムプロンプト

Skillsとカスタムコマンドは、プロンプトに/skill-nameを含めれば実行前に展開されます。/loginのようにターミナルの画面が前提の組み込みコマンドは使えません。一方で、v2.1.205以降は/model sonnetのように引数で値を渡す形が通り、/config thinking=falseのようなkey=valueで設定も変えられます。

次のスクリプトは、PRの差分をパイプで渡し、レビュー役を追加して結果をJSONで受け取ります。

gh pr diff "$1" | claude -p \
  --append-system-prompt "あなたはセキュリティエンジニアです。脆弱性の観点でレビューして" \
  --output-format json

"$1"には、bash review.sh 123のように渡した最初の引数が入ります。--append-system-promptは既定のプロンプトを残して指示を足し、--system-promptは丸ごと入れ替えます。後者はClaude Codeの標準の振る舞いも失われます。違いはappend-system-promptとsystem-promptの違いにまとめました。

--output-format jsonの結果にはtotal_cost_usdが含まれ、モデル別の内訳も付くので、スクリプト側で費用を集計できます。値はクライアント側の見積もりで、請求額とずれることがあります。

会話を続ける

直近の会話は--continueで続けます。複数の会話を並行させるなら、セッションIDを取り出して--resumeで指定します。

session_id=$(claude -p "レビューを始めて" --output-format json | jq -r '.session_id')
claude -p "続きをお願い" --resume "$session_id"

v2.1.223以降は、2つのコマンドを別のディレクトリで実行してもセッションを見つけられます。それ以前は、同じプロジェクトのディレクトリで実行する必要がありました。

終了時の挙動 — バックグラウンドタスクとSIGTERM

-pの実行中にBashツールでdevサーバーやウォッチビルドを起動すると、最終結果を返してから約5秒後にそのシェルが終了します。結果の直後に出力を出すタスクのための猶予です。バックグラウンドのサブエージェントとワークフローは別扱いで、結果が最終出力の一部になるため、完了までclaude -pは終了しません。

待ちは、アイドルが10分続くと打ち切られます。打ち切られると残りの処理は止まり、途中の結果は捨てられます。上限はCLAUDE_CODE_PRINT_BG_WAIT_CEILING_MSで変え、0なら無制限です。Monitorの監視は、タイムアウトか10分の上限のどちらか早い方まで待たれ、既定の監視タイムアウトは5分です。

プロセスをSIGTERMで止めると、終了コードは143です。実行中だったターンは未完了のままで、結果も記録されません。止める前にターンを終わらせたいなら、SIGINTを送るか、Agent SDKのinterrupt()を呼びます。実行中だったBashコマンドのプロセスツリーは終了され、SessionEndフックだけが走ります。再開時に中断したターンを続けさせるには、CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1を設定します。

CIで止まる原因の見分け方

プロンプトを渡し忘れた: 引数も標準入力もないと、上で見たInput must be provided ...で止まります。空白だけのプロンプトも、v2.1.229以降はAPIに送る前にError: Input contained only whitespaceで弾かれます。変数から組み立てるスクリプトは、空文字列の検査を入れておくと原因の切り分けが早くなります。

認証が切れた: 保存済みのOAuthログインが切れると、Failed to authenticate: OAuth session expired and could not be refreshedが標準出力に出ます。対話でのサインインはできないので、認証の節で決めた環境変数に切り替えます。

操作が拒否されて作業が進まない: 権限モードがdefaultで、許可していない操作に当たった可能性があります。--permission-modeと--allowedToolsを見直し、拒否の一覧を確かめたいならstream-jsonでpermission_denialsを読みます。

手元とCIで挙動が違う: 手元の~/.claudeにあるフックやMCPサーバーがCIには存在しないか、逆にCIのリポジトリ側の設定が取り込まれた可能性があります。--bareで文脈を固定すると、差が残るのは明示した設定だけになります。

まとめ

読ませるリポジトリが第三者のものなら--bare、自分のリポジトリで手元と同じ文脈が要るなら--bareなしで--permission-modeを明示する、という分け方があります。後者でも、CIに存在しない~/.claudeのフックやMCPサーバーに結果が左右されていないかは、最初の1回で見ておくと安心です。

この記事を共有:XはてブLinkedIn