Claude Media
Claude Codeでxcodebuildのエラーを直す — -quietとxcresultで出力を絞る

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行

Xcode 26.6、macOS向けSwift Package、エラー2件・警告1件

オプションなしの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 -quiet

macOS向けのビルドなら、'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.sh

Claude 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は、直接実行された場合の保険として足せます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/hooks-guide
  2. code.claude.com/docs/en/hooks
  3. code.claude.com/docs/en/tools-reference
  4. developer.apple.com/library/archive/technotes/tn2339/_index.html
この記事を共有:XはてブLinkedIn