やりたいこと
請求書 200 枚を勘定科目で仕分ける。問い合わせメール 500 件を分類する。議事録 30 本から決定事項だけ抜く。
1 件ずつチャットに貼り付けるのは業務ではありません。 コマンドとして回して、結果を表で受け取りたい。
素朴にやると穴が空く
穴1: 全部まとめて渡すと落ちる
「フォルダごと渡せばいい」は最初に潰れます。
Claude Code の -p にパイプできる stdin は 10MB が上限で、超えるとエラーを出して 0 以外のステータスで終了します。出典は、より大きな入力はファイルに書いてプロンプトでそのパスを参照するよう案内しています。
そもそも一括で渡すと、1 件だけ判断を間違えたときにどれが間違ったのか分からなくなります。 1 件ずつ回すのが基本形です。
穴2: 終了コード 0 なのに失敗している
これがいちばん危ない穴です。Claude Code の非対話実行について、出典はこう書いています。
- 不正なフラグを渡した場合は、実行開始前に stderr へエラーを報告する
- 実行中の失敗(認証情報が無いなど)は、結果として stdout に出力される
つまり認証が切れていた場合、エラーメッセージが「処理結果」として stdout に出てきます。 それを 200 件分ファイルに書き出せば、**200 件すべてが「エラー文が書かれた成果物」**になります。終了コードだけ見ていると気づけません。
穴3: Codex は Git リポジトリの中でしか動かない
破壊的な変更を防ぐため、コマンドは Git リポジトリ内で実行する必要がある
請求書フォルダは普通 Git リポジトリではありません。codex exec --skip-git-repo-check で回避できますが、この安全装置が何のためにあるかを理解してから外してください。
加えて、codex exec は既定で read-only sandbox で動きます。 ファイルを書き出す処理は、--sandbox workspace-write を明示しない限り黙って何も残しません。
穴4: 出力の形が毎回違う
自由記述で返させると、1 件目は「勘定科目: 消耗品費」、50 件目は「これは消耗品費に該当すると思われます」になります。表に落とせません。
3 ツールとも、出力の形を固定する仕組みを持っています。
| ツール | フラグ | 挙動 |
|---|---|---|
| Claude Code | --output-format json + --json-schema | 構造化出力は structured_output フィールドに入る |
| Codex | --output-schema | JSON Schema に適合する最終応答を要求する |
| Gemini CLI | --output-format json | response / stats / error を持つ単一の JSON オブジェクト |
Claude Code の --json-schema には注意点があります。format キーワード("format": "email" など)は受け付けるが、注釈として扱い強制しません。 メールアドレス形式を指定しても検証はされません。
穴5: 失敗の種類が区別できない
Gemini CLI は終了コードを分けています。
| コード | 意味 |
|---|---|
0 | 成功 |
1 | 一般的なエラーまたは API 失敗 |
42 | 入力エラー(不正なプロンプトや引数) |
53 | ターン数の上限超過 |
42 はこちらの書き方が悪い、53 は処理が重すぎる、1 は向こう側の問題——原因が違えば直し方も違います。 全部まとめて「失敗」にすると、200 件回した後に何を直せばいいか分かりません。
穴6: 1 件ごとに重い荷物を積み直している
--bare を付けない claude -p は、対話セッションと同じコンテキストを読み込みます(作業ディレクトリの設定、~/.claude の設定、CLAUDE.md、MCP サーバー、hooks、skills、auto memory)。
200 件のループでは、200 回それを読み直します。 遅くなり、そのぶん課金されます。
--bare を付けると全部飛ばして起動が速くなりますが、代わりに:
- OAuth 認証情報もシステムキーチェーンも読まない。
ANTHROPIC_API_KEYを設定するか--settingsでapiKeyHelperを渡す必要がある - 使えるツールは Bash・ファイル読み取り・ファイル編集のみ
出典は --bare をスクリプト・SDK 呼び出しの推奨モードとし、将来のリリースで -p の既定になると書いています。
手順
1. まず 1 件で通す
ループを書く前に、1 件だけ手で流します。ここで出力の形が安定していなければ、200 件回しても安定しません。
claude -p "この請求書の発行元・金額・勘定科目を判定してください" < invoices/001.txt2. 出力の形を固定する
cat > schema.json <<'JSON'{ "type": "object", "properties": { "issuer": { "type": "string" }, "amount": { "type": "number" }, "account": { "type": "string" }, "confidence": { "type": "string", "enum": ["high", "low"] } }, "required": ["issuer", "amount", "account", "confidence"]}JSONconfidence のような「自信がない」を表せる欄を必ず入れてください。 入れないと、判断に迷った件も断定的な答えとして返ってきます。後から人間が確認する対象を絞れるかどうかが、実務では効きます。
スキーマが不正な場合は Error: --json-schema is not a valid JSON Schema に続けて診断が出て終了します(v2.1.205 より前は黙って無視して非構造化テキストを返していました)。
3. 1 件ずつ回して、成否を分けて記録する
#!/usr/bin/env bashset -uo pipefail # -e は付けない。1 件の失敗で全体を止めないため
mkdir -p out errfor f in invoices/*.txt; do name=$(basename "$f" .txt)
if claude -p --bare \ --output-format json --json-schema schema.json \ "この請求書の発行元・金額・勘定科目を判定してください" \ < "$f" > "out/$name.json" 2> "err/$name.log" then # 穴2 の対策。終了コードが 0 でも中身がエラーのことがある if ! jq -e '.structured_output.issuer' "out/$name.json" > /dev/null 2>&1; then mv "out/$name.json" "err/$name.badoutput.json" echo "FORMAT $name" >> result.log continue fi echo "OK $name" >> result.log else echo "FAIL($?) $name" >> result.log fidone
sort result.log | uniq -c -w7要点は 3 つです。
set -eを使わない。 1 件の失敗で 199 件を止めない- stdout と stderr を別ファイルに分ける。 混ぜると穴2 を見つけられない
- 終了コードだけでなく中身も検査する。 期待するフィールドが無ければ失敗扱いにする
4. Codex / Gemini CLI での書き方
# Codex — 進捗は stderr、最終メッセージだけが stdout に出るcodex exec --skip-git-repo-check \ --output-schema schema.json \ -o "out/$name.txt" \ "この請求書を分類してください" < "$f"-o / --output-last-message はファイルに書いたうえで stdout にも出力します。
# Gemini CLI — 終了コードで原因を分けるgemini --output-format json -p "この請求書を分類してください" < "$f" > "out/$name.json"case $? in 0) echo "OK $name" ;; 42) echo "INPUT $name" ;; # プロンプトか引数が悪い 53) echo "TURNS $name" ;; # 処理が重すぎる *) echo "FAIL $name" ;;esac5. コストを見ておく
claude -p --output-format json のペイロードには total_cost_usd とモデル別のコスト内訳が含まれます。
jq -s 'map(.total_cost_usd) | add' out/*.jsonただしこれはクライアント側の推定値で、実際の請求とは異なりえます。 「だいたいいくらの規模か」を掴むためのもので、精算に使う数字ではありません。
Gemini CLI の JSON 出力には stats(トークン使用量と API レイテンシ)が入ります。
確認方法
# 1. 全件処理されたかls invoices/*.txt | wc -l && ls out/*.json | wc -l
# 2. 中身がエラー文になっていないか(穴2)grep -l -i "error\|credential\|not authenticated" out/*.json
# 3. 判断に迷った件を人間が見るjq -r 'select(.structured_output.confidence == "low") | input_filename' out/*.json2 が 1 件でも引っかかったら、そのバッチは全部やり直しです。 認証切れは途中から一斉に起きるので、部分的な救済は普通できません。
残る限界
- 正しさは検証されない。 形式が揃っていることと、判定が合っていることは別。
confidence: lowを人間が見る運用が前提になる - コスト表示はクライアント側の推定で、請求額ではない
--bareは速いが認証を自分で用意する必要があり、ツールも 3 つに絞られる。MCP 経由で社内システムを見る処理は--bareでは動かないclaude -pの実行中に開始したバックグラウンド Bash タスクのシェルは、最終結果を返して stdin が閉じた約 5 秒後に終了させられる。 時間のかかる後処理を投げっぱなしにできない- Codex の
--skip-git-repo-checkは安全装置を外す操作。書き込みを伴うバッチでは、対象フォルダを Git 管理下に置くほうが安全
根拠
facts/claude-code/headless.md—-pの終了コード、stdin 10MB 上限、--bareの挙動と認証、--output-format/--json-schema、バックグラウンドタスクの 5 秒facts/codex/non-interactive-mode.md—codex exec、stdout / stderr の分担、read-only sandbox 既定、Git リポジトリ要求、--output-schema、-ofacts/gemini-cli/headless.md— 起動条件、JSON 出力の形、終了コード 0 / 1 / 42 / 53