Claude Codeでxcodebuildのエラーを直す — -quietとxcresultで出力を絞る
xcodebuildが失敗すると長いログがClaude Codeに渡り、肝心のerror行が抜粋で切れます。-quiet、xcresult、PostToolUseFailure hookでerror行だけを返す手順です。
xcodebuildのビルドエラーをClaude Codeに直させるとき、ログの長さが最初の壁になります。ビルドが失敗したコマンドの出力は、約10,000字を超えると先頭と末尾の抜粋に切り詰められます。error行が中ほどにあると、Claudeには届きません。
対策は3段です。-quietで出力そのものを減らし、-resultBundlePathで結果をxcresultに残し、xcrun xcresulttoolでerrorだけを取り出します。取り出す処理は、ラッパースクリプトかPostToolUseFailure hookに載せます。
失敗したxcodebuildの出力はどう切られるか
Claude CodeのBashツールは、コマンドの結果を成功と失敗で別々に扱います。公式のtools-referenceにある出力上限は次のとおりです。
| 結果 | Claudeに渡るもの |
|---|---|
| 成功 | Claudeに渡るもの既定で約30,000字までインライン。超えるとファイルのパスと先頭2,000字のプレビュー |
| 失敗 | Claudeに渡るもの約10,000字までインライン。超えると同じ大きさの先頭と末尾の抜粋で、ファイルのパスは付かない |
xcodebuildはビルドに失敗すると、終了コード65で終わります。この記事で試した環境(Xcode 26.6)での観測です。終了コード1だけを正常扱いするコマンドはgrepやfindなどに限られるため、xcodebuildの失敗は常に「失敗」の側に入ります。
つまり、直したい場面ほど出力が切り詰められます。手元で試した小さなSwift Packageでも差は明確でした。ソースには型エラーを2つと未使用変数の警告を1つだけ入れています。
同じ失敗ビルドの出力量
オプションなし
35,188字
179行
-quiet付き
7,700字
26行
error行だけ抜き出し
約400字
2行
オプションなしの35,188字は、上限の約10,000字の3倍を超えています。実際のアプリは依存ライブラリも多く、行数はもっと増えます。error行が抜粋の外に出る条件は、このデモよりずっと早くそろいます。
出力上限そのものを上げるbashOutputMaxCharsもあります。ただし失敗時のインライン上限が変わるとは、tools-referenceに書かれていません。設定の詳細はbashOutputMaxCharsの解説にまとめています。ログを増やしてから上限を上げるより、ログを減らすほうが筋の良い順序です。
-quietは何を消して何を残すか
xcodebuild -helpには、-quietが「警告とエラー以外は何も出力しない」オプションとして載っています。ビルド途中の=== BUILD TARGET ...や、コンパイラ呼び出しの行が消えます。
ただし、-quietを付けても残る行があります。手元で失敗ビルドを走らせた結果は次のとおりでした。
| 行 | -quietで |
|---|---|
| コンパイラのerror・warning(ソース行とカレット付き) | -quietで残る |
Writing result bundle at path:とそのパス | -quietで残る(-resultBundlePath指定時) |
Using the first of multiple matching destinationsの警告と候補一覧 | -quietで残る |
Failed frontend command:以下の巨大な1行 | -quietで残る |
** BUILD FAILED ** | -quietで残る |
7,700字のうち大半は、Failed frontend command:に続く1行です。swift-frontendに渡した全オプションとパスが並び、エラーの原因はそこに書かれていません。-quietだけでは、この1行は消えません。
成功時の挙動も確かめました。警告1件だけのビルドでは、-quiet付きの出力は警告の3行と、上記のdestination警告やresult bundleの案内だけです。** BUILD SUCCEEDED **は出ませんでした。成功かどうかは、終了コードで判断させる必要があります。
destinationの警告は、-destinationにarchまで書くと減らせます。複数の候補が一致することが警告の原因だからです。
xcodebuild -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' \
build -quietmacOS向けのビルドなら、'platform=macOS,arch=arm64'のようにarchまで指定します。
xcresultからerror行だけを取り出す
-quietで減らしても、Failed frontend command:の行は残ります。そこで、ビルド結果を-resultBundlePathでxcresultに書かせ、構造化された結果からerrorだけを読む方法に切り替えます。
xcodebuildの結果バンドルは、-resultBundlePathでパスを指定して保存できます。読み出すにはxcrun xcresulttool get build-resultsを使います。--pathに結果バンドルを渡し、--compactを付けるとJSONが1行で返ります。
xcodebuild -scheme MyApp -destination 'platform=macOS,arch=arm64' \
build -quiet -resultBundlePath /tmp/build.xcresult
xcrun xcresulttool get build-results --path /tmp/build.xcresult --compact返るJSONにはerrorCount、errors、warnings、statusなどのキーがあります。errorsの各要素はissueType、message、sourceURLを持ちます。次は失敗ビルドの実出力の抜粋です(パスは置き換えています)。
{
"errorCount": 2,
"errors": [
{
"issueType": "Swift Compiler Error",
"message": "Cannot convert value of type 'String' to specified type 'Int'",
"sourceURL": "file:///path/to/MyApp/Sources/Demo/Demo.swift#EndingColumnNumber=25&EndingLineNumber=3&StartingColumnNumber=25&StartingLineNumber=3&Timestamp=814801220.958648"
}
],
"status": "failed"
}注意点が1つあります。sourceURLに入る行番号は0始まりです。ソースの4行目のエラーがStartingLineNumber=3で返りました。そのままClaudeに渡すと、1行手前を見に行きます。スクリプトで1を足してから渡します。
また、結果の取り出しにxcresulttool getをオプションなしで使う旧来の書き方は避けます。xcresulttool getの既定のサブコマンドobjectは、help上で「将来のリリースで削除される」と案内されています。get build-resultsと、テスト用のget test-resultsが現行の入口です。
ラッパースクリプトでerror行だけを返す
ここまでをまとめたスクリプトです。xcodebuildを-quietと-resultBundlePath付きで実行し、失敗時だけerror行をファイル:行: error: メッセージの形で返します。全文ログは一時ディレクトリに残し、場所だけを知らせます。
#!/bin/bash
# scripts/xcb-errors.sh
# 使い方: scripts/xcb-errors.sh -scheme MyApp -destination '...' build
set -u
WORK=$(mktemp -d)
BUNDLE="$WORK/build.xcresult"
xcodebuild "$@" -quiet -resultBundlePath "$BUNDLE" > "$WORK/full.log" 2>&1
CODE=$?
echo "xcodebuild exit code: $CODE"
if [ "$CODE" -ne 0 ]; then
xcrun xcresulttool get build-results --path "$BUNDLE" --compact \
| jq -r '.errors[] |
(.sourceURL // "") as $u |
($u | sub("^file://"; "") | sub("#.*"; "")) as $f |
($u | capture("StartingLineNumber=(?<n>[0-9]+)")? | .n | tonumber + 1) as $l |
"\($f):\($l // "?"): error: \(.message)"'
echo "full log: $WORK/full.log"
fi
exit "$CODE"同じデモを流すと、Claudeに返る出力は次の3行になります(パスは置き換えています)。
xcodebuild exit code: 65
/path/to/Demo/Sources/Demo/Demo.swift:4: error: Cannot convert value of type 'String' to specified type 'Int'
/path/to/Demo/Sources/Demo/Demo.swift:5: error: Cannot convert value of type 'Int' to expected argument type 'String'error行だけなら約400字で、全文ログの35,188字に対して1%ほどです。行番号は、手元のソースの4行目と5行目に一致しました。全文ログのパスも最後に付くので、足りなければClaudeが自分で開けます。
スクリプトを使わせる指示は、CLAUDE.mdに書きます。ビルドコマンドを直接書く代わりに、スクリプト経由を既定にします。
## ビルド
- ビルドは必ず `scripts/xcb-errors.sh -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 16' build` で行う
- 終了コードが0以外なら、出力のerror行の場所を直し、同じコマンドで再実行する
- error行だけで原因が分からないときに限り、出力末尾の full log を開く
- `xcodebuild` を直接実行しない許可ルールも、スクリプトの先頭に合わせて絞れます。Bash(scripts/xcb-errors.sh *)の1本で足ります。xcodebuildのarchiveやcleanまで許可が広がらないため、SwiftUIアプリ開発の手順で触れた「広く取る」「狭く取る」の選択にも収まります。
PostToolUseFailure hookで取りこぼしを拾う
CLAUDE.mdの指示は守られないことがあります。Claudeがxcodebuildを直接実行する場合に備えて、hookを安全網として足せます。
使うイベントはPostToolUseFailureです。ツールが失敗したあとに動き、additionalContextを返すと、エラーと並べてClaudeに渡されます。hookの入力にはエラー文字列がerrorキーで入ります。Bashなら先頭行はExit code Nの形です。matcherでBashに絞り、ifでxcodebuildの呼び出しだけに限ります。
{
"hooks": {
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(xcodebuild *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/xcb-hook.sh"
}
]
}
]
}
}hook本体は、errorの中から-quietが残すWriting result bundle at path:の次の行を探し、そのxcresultからerrorを読みます。
#!/bin/bash
# .claude/hooks/xcb-hook.sh
INPUT=$(cat)
BUNDLE=$(echo "$INPUT" | jq -r '.error' \
| sed -n '/Writing result bundle at path:/{n;s/^[[:space:]]*//;p;}' | head -1)
[ -z "$BUNDLE" ] && exit 0
[ -d "$BUNDLE" ] || exit 0
SUMMARY=$(xcrun xcresulttool get build-results --path "$BUNDLE" --compact \
| jq -r '.errors[] | "\(.sourceURL | sub("^file://"; "") | sub("#.*"; "")): \(.message)"')
jq -n --arg ctx "xcodebuild errors from xcresult:
$SUMMARY" \
'{hookSpecificOutput: {hookEventName: "PostToolUseFailure", additionalContext: $ctx}}'失敗ログを模した入力で流したところ、返ったJSONのadditionalContextは約500字でした。2件のerrorとソースのパスが入っています。hookの出力は、標準出力にJSONだけを置く必要があります。シェルの起動時にechoされる行などが混じると、JSONとして読まれません。
このhookには限界が3つあります。
- 元の長い出力は消えません。
PostToolUseFailureが返せるのはadditionalContextだけで、失敗した結果の置き換えはできません。置き換え用のupdatedToolOutputはPostToolUseの機能です -resultBundlePathが指定されていないと動きません。上のスクリプトはパスが見つからなければ何も返さず終了します。CLAUDE.mdで-resultBundlePathの付与を指示するか、ラッパー経由に寄せますadditionalContextは10,000字が上限です。超えた分はファイルに保存され、先頭2,000字のプレビューだけが渡ります。errorが数百件ある状況では、headで件数を絞ります
hookが動いたかを確かめる
hookが成功しても、画面には何も出ません。動作の確認は、スクリプトを単体で試すのが早道です。失敗ログを模したJSONを標準入力に渡し、additionalContextが返るかを見ます。パスには実在するxcresultを指定します。存在しないパスでは、スクリプトは何も返さず終了します。
echo '{"error":"Exit code 65\nWriting result bundle at path:\n\t/tmp/build.xcresult\n"}' \
| .claude/hooks/xcb-hook.shClaude Codeの中では、Ctrl+Oでトランスクリプト表示を開きます。<hook name> hook errorの通知があれば、hookが失敗したまま処理が進んでいます。終了コードや標準エラーまで見たいときは、claude --debug-file /tmp/claude.logで起動してログを追います。設定を書いたのに/hooksに出ない場合は、JSONの末尾カンマやコメントが原因になりやすい点です。
3つの方法の使い分け
| 方法 | 必要なもの | 向く場面 |
|---|---|---|
-quietだけ | 必要なものCLAUDE.mdの1行 | 向く場面小さなプロジェクト。巨大な1行は残る |
| ラッパースクリプト | 必要なものスクリプトとCLAUDE.md | 向く場面毎回確実にerror行だけにしたい |
PostToolUseFailure hook | 必要なもの設定とスクリプト | 向く場面直接実行された場合の保険 |
最初に試すなら、ラッパースクリプトです。Claudeの行動に頼らず、出力の形が決まるからです。hookは追加の安全網にとどめるのが無難です。Gradleでも同じ発想で、Gradleビルドの出力を絞る記事では-qと--console=plainを使っています。Mavenの場合はMavenビルドの検証にまとめています。
つまずきやすい点
スキームが見つからない
Swift Packageをxcodebuildで開くと、スキーム名はパッケージ名ではなくDemo-Packageのように末尾が付いた形になることがあります。手元の例では、ライブラリだけのパッケージで確認しました。xcodebuild -listで名前を確かめてから、CLAUDE.mdに書きます。
テストの失敗はbuild-resultsに出ない
get build-resultsはビルドのerrorとwarningを返します。XCTestやSwift Testingの失敗は、xcresulttool get test-resultsのほうが対象です。helpでは「実行されたテストと、報告されたすべての失敗」を返すと説明されています。xcodebuild testを使う場合は、ラッパーにこのコマンドを足します。
警告だけのビルドが成功扱いか分からない
-quietの成功時は** BUILD SUCCEEDED **が出ないため、出力が警告だけだと成否が曖昧に見えます。ラッパーの先頭行に終了コードを出しているのはこのためです。CLAUDE.mdにも「終了コードで成否を判断する」と書いておきます。
Xcodeの組み込み機能でClaudeを使う方法は、XcodeでClaudeを使う方法で扱っています。
まとめ
xcodebuildの失敗ログは、Claude Codeの失敗時の上限(約10,000字)を簡単に超えます。-quietは出力を減らしますが、巨大なFailed frontend command:の1行までは消しません。error行だけを確実に渡すなら、xcresultを経由するラッパーが近道です。hookは、直接実行された場合の保険として足せます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。