Claude CodeでPlatformIOのpio check指摘を修正と抑制に仕分ける
pio checkが出すcppcheckとclang-tidyの指摘を、Claude Codeに修正・抑制・保留へ仕分けさせるための入力の作り方を、platformio.iniとCLAUDE.mdの設定例でまとめます。
組込みCの静的解析は、初めて回すと指摘が大量に並びます。その全部を直す必要はありません。直すもの、理由を書いて抑えるもの、人に聞くものに分けて初めて、次の作業が決まります。
この仕分けはClaude Codeに任せられます。ただし、出力をそのまま貼って「直して」と頼むと、抑制コメントだらけになるか、設定を緩めて指摘ごと消すかのどちらかに寄りがちです。分かれ目は、頼む前に渡す入力の形にあります。
この記事では、pio checkでcppcheckとclang-tidyを並べて走らせ、結果を修正・抑制・保留に振り分けさせるための入力を、platformio.ini、CLAUDE.md、permissionsの3点で固定します。pio checkの基本とビルド・テストの分担はClaude CodeでPlatformIOのファームウェアを開発する手順にあります。ここでは指摘の中身の扱いに絞ります。
仕分けに必要な入力は3つ
pio checkは、PlatformIOのプロジェクトに対して静的解析を実行するコマンドです。何も設定しなければCppcheckが使われます。出力の1行には、場所、重大度、分類、メッセージ、ID(末尾の角括弧)が並びます。PlatformIOのCppcheckの説明にある例が次の形です。
src\Blink.cpp:17: [low:style] The function 'loop' is never used. [unusedFunction]Claude Codeに仕分けさせるには、この1行から読み取れる情報に加えて、2つが要ります。
仕分けの入力に必要な3つ
指摘そのもの
場所・重大度・ID・メッセージ。
pio checkの出力に含まれます。周辺のコード
その行の前後と、呼び出し元。Claude Codeが自分で読みに行けます。
プロジェクトの判断基準
どれを直し、どれを抑えてよいか。出力のどこにも書かれていないので、人が文章にして渡します。
3つ目が欠けると、Claude Codeは一般論で判断します。一般論の「安全側」は、組込みでは「全部直す」か「全部抑える」になりがちです。基準を書いたファイルを読ませるのが、この記事の中心です。
重大度は3段で、仕分けの第一軸になる
PlatformIOは欠陥の重大度を3段に分けています。
| 重大度 | 意味 |
|---|---|
| high | 意味バグの可能性がある問題 |
| medium | 意味将来のバグを防ぐための防御的プログラミングの提案 |
| low | 意味未使用関数、冗長なコード、const化など、整理と性能に関する問題 |
この区分は、そのまま仕分けの第一軸に使えます。highは直す候補、lowは整理の候補、mediumは変更の大きさ次第、という出発点です。
重大度はcheck_severityで報告対象を絞れます。既定はlow、medium、highの3つすべてです。絞ると指摘の数は減りますが、絞った分は仕分けにも上がってきません。最初はすべて出して全体を見てから、運用で外す重大度を決めるほうが、取りこぼしに気づけます。
platformio.iniで入力を整える
cppcheckとclang-tidyを両方回すには、check_toolに並べます。PlatformIOの説明では、複数のツールで調べたいときに使うオプションです。ツールごとのフラグは、行頭にツール名を書いて分けられます。
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
check_tool = cppcheck, clangtidy
check_flags =
cppcheck: --enable=warning,style,performance --inline-suppr
clangtidy: --checks=-*,bugprone-*,cert-*,clang-analyzer-*
check_skip_packages = yes各行の意図を書きます。
--inline-suppr: PlatformIOの説明では、インライン抑制は追加フラグを指定しない場合に既定で有効です。cppcheck:で別のフラグを渡すと、この前提が変わる可能性があるので、明示しておきます。抑制コメントが効かなくなると、仕分けの結果が再実行で再現しませんclangtidy: --checks=...: 既定ではすべてのチェックが有効です。仕分けの最初は、バグに直結するbugprone-、cert-、clang-analyzer-に絞ると、指摘の量が扱える範囲に収まります。readability-やmodernize-は、バグ以外の指摘が増える群ですcheck_skip_packages = yes: フレームワークやツールチェーンのコードを検査対象から外します。解析ツールが独自のプリプロセッサで正常なコードを読めず、レポートが空や中途半端になるのを避ける用途の設定です
ここに--fixは入れません。clang-tidyの--fixは、提案された修正をその場でソースに適用します。仕分けの入力を作る段階でソースが書き換わると、「何を直し、何を抑えるか」を決める前に変更が混ざり、差分の追跡ができなくなります。
仕分けの基準をCLAUDE.mdに書く
判断基準は、出力に書かれていないので文章にします。CLAUDE.mdに書けば、セッションをまたいで同じ基準で仕分けられます。次は、記事の例示として組んだ規約の断片です。
# 静的解析の仕分け規約
`pio check` の指摘は次の3区分に分ける。
- 修正: 重大度 high。medium のうち、1箇所の小さな変更で消えるもの
- 抑制: 次の2つに限る
1. Arduino フレームワークが呼ぶ関数の unusedFunction(setup / loop)
2. 誤検知と説明できるもの。理由を同じコメントに書く
- 保留: 判断がつかないもの。コードは触らず、表に理由だけ書く
抑制のルール:
- 対象の指摘のIDを必ず指定する。ID なしの NOLINT は書かない
- 抑制コメントには理由を付ける
- 抑制は指摘の行のすぐ前か同じ行に置く。範囲抑制は使わない
触ってはいけないもの:
- platformio.ini の check_ で始まる設定
- check_severity を下げて指摘を消すこと
- 検査対象を外して指摘を消すこと
作業の終わりは、同じ `pio check` を再実行し、
修正した分の指摘が消え、新しい指摘が増えていないことで確認する禁止事項の3行が、実際の失敗を防ぎます。指摘が消えない状況で最短の手は、重大度の引き下げや検査対象の除外です。Claude Codeがそれを選べないよう、規約で先に塞いでおきます。
規約は強制力のないお願いです。platformio.iniの変更は、PRの差分で必ず目を通す項目にしておくと、すり抜けを拾えます。CLAUDE.mdの書き方と長さの注意はCLAUDE.mdが途中までしか読まれない上限と確認法が参考になります。
抑制コメントの書き方はツールごとに違う
抑制の書式はcppcheckとclang-tidyで別物です。規約に「理由を付ける」と書くだけでは、どちらの書式にも合わせられません。書式を表にして、規約から参照させます。
| ツール | 書式 | 効く範囲 |
|---|---|---|
| cppcheck | 書式// cppcheck-suppress <ID> を警告行の前に置く | 効く範囲次の行。--inline-supprが必要 |
| cppcheck | 書式--suppress=<ID>:<ファイル>:<行> をフラグで渡す | 効く範囲指定したファイルと行 |
| clang-tidy | 書式// NOLINT(<チェック名>) | 効く範囲そのコメントのある行だけ |
| clang-tidy | 書式// NOLINTNEXTLINE(<チェック名>) | 効く範囲次の1行 |
| clang-tidy | 書式// NOLINTBEGIN(<チェック名>) と NOLINTEND | 効く範囲2つのコメントに挟まれた行 |
PlatformIOの説明に載っているcppcheckの例は、Arduinoのsetupとloopです。フレームワークのソースは既定では検査されないので、呼び出し元が見えず、unusedFunctionとして報告されます。
// cppcheck-suppress unusedFunction
void setup()
{
// ...
}
// cppcheck-suppress unusedFunction
void loop()
{
// ...
}clang-tidyのNOLINTは、コメントの後ろに:で理由を続けられます。clang-tidyのドキュメントには、// NOLINT: Allow implicit conversion from char, because <some valid reason>という形の例があります。規約の「理由を付ける」は、この書き方に合わせれば機械的に守れます。
clang-tidyには守らせたい細則が2つあります。
- NOLINTとかっこの間に空白を置くと、チェック名の指定として読まれず、すべての警告を黙らせるNOLINTとして扱われます。チェック名を絞ったつもりが、全部を抑えてしまいます
NOLINTBEGINは同数のNOLINTENDと対にし、引数も一致させます。対応が崩れると、clang-tidyがclang-tidy-nolintのエラーを出します
1つ目は特に見落としやすいので、規約には「NOLINTの直後にかっこを空白なしで続ける」と書き足しておくと、レビューで拾えます。
実行して、表を作らせる
入力が整ったら、まず人が一度pio checkを回し、出力が仕分けに足りる形かを見ます。
pio check -e esp32dev出力をファイルに落とす方法もあります。pio checkには--json-outputがあり、結果をJSONで返せます。JSONのキーの構造は、PlatformIOのドキュメントに書かれていないので、実際に出力して形を確かめてから、Claude Codeに渡します。
そのうえで、Claude Codeには、コードを触る前に表を作らせます。
pio check -e esp32dev を実行し、全指摘を CLAUDE.md の「静的解析の仕分け規約」に
従って修正・抑制・保留に分けてください。まだコードは変更せず、
次の列の表だけを出してください。
場所 / ツールとID / 重大度 / 区分 / 根拠(該当コードのどこを見たか)表の「根拠」の列を必須にします。ここが空の行や「一般的に」で済ませた行は、仕分けが読み込み不足のまま行われたサインです。出力の形は次のようになります。IDと内容はすべて例示用の仮のものです。
| 場所 | ツールとID | 重大度 | 区分 | 根拠 |
|---|---|---|---|---|
| src/main.cpp:17 | ツールとIDcppcheck:<ID> | 重大度low | 区分抑制 | 根拠Arduinoのsetupで、フレームワークから呼ばれる |
| src/sensor.cpp:42 | ツールとIDclangtidy:<チェック名> | 重大度high | 区分修正 | 根拠戻り値を検査せずに配列の添字に使っている |
| lib/rtos/queue.c:88 | ツールとIDcppcheck:<ID> | 重大度medium | 区分保留 | 根拠割り込みハンドラとの共有変数で、並行性の判断が要る |
表を人が見て、区分の食い違いを直します。保留の行に判断を書き足すのも、この段階です。そのあとで初めて、修正と抑制をコードに反映させます。
反映と再実行で閉じる
表が確定したら、反映を段階に分けます。
仕分けの反映手順
- 1
修正の行だけ直す
区分が「修正」の行を、重大度の高い順に直させます。1回の変更に混ぜるのは、同じファイルの指摘までにします。
- 2
抑制の行にコメントを足す
区分が「抑制」の行に、規約の書式でコメントを足させます。IDとチェック名、理由が揃っているかを差分で確かめます。
- 3
同じ pio check を再実行する
最初と同じコマンドで回し、修正した分の指摘が消え、新しい指摘が増えていないことを見ます。
- 4
pio run で通ることを確かめる
静的解析を直した変更でビルドが壊れていないかを確認します。
pio run -e esp32dev -sでビルドが通ることを確かめます。
手順3の「新しい指摘が増えていない」が見落とされがちです。修正で別の指摘が生まれることがあり、件数が減ったかだけを見ると、置き換わっただけの変更を通してしまいます。
permissionsでは、clang-tidyの--fixを渡す経路を塞いでおきます。
{
"permissions": {
"allow": [
"Bash(pio check *)",
"Bash(pio run -e *)"
],
"deny": [
"Bash(pio check *--fix*)",
"Bash(pio run *upload*)"
]
}
}denyはallowより優先されるので、pio check *を許可しても、--fixを含む呼び出しは止まります。設定ファイルの全体像はClaude Code settings.json完全ガイドにあります。
仕分けが崩れる3つの場面
落とし穴
フラグを足したら抑制コメントが効かなくなった
check_flagsにcppcheckのフラグを追加した直後から、抑制したはずの指摘が復活することがあります。インライン抑制が既定で有効なのは、追加フラグを指定しない場合です。--inline-supprを明示してから、再実行して消えることを確かめます。
落とし穴
レポートが空で、仕分ける対象がない
pio checkが空やごく少数のレポートを返すときは、解析ツールがコードを正しく読めていない可能性があります。解析ツールの独自プリプロセッサが、正常なコードを解釈できない場合の症状として、PlatformIOの説明に記載があります。check_skip_packagesでサードパーティのコードを外し、自分のソースだけを見て切り分けます。
落とし穴
NOLINTが増えるだけで指摘は減らない
抑制の件数が毎回増えていくなら、規約の「抑制は2つに限る」が守られていません。再実行後に、抑制コメントの数を前回と比べる項目を、レビューの確認事項に足します。
もう一つ、組込み特有の注意があります。解析ツールは、割り込みハンドラや、ハードウェアが書き換えるレジスタの存在を、プロジェクトの外の知識なしには推測できません。こうした行の指摘は、Claude Codeが「誤検知」と判断しても、人の確認が済むまで保留に置くのが安全です。規約に「割り込みと共有する変数に関する指摘は、保留にする」と一行足すだけで、判断を人に戻せます。
まとめ
静的解析の指摘を仕分けられるかは、Claude Codeの力量より、渡す入力で決まります。重大度を第一軸に、抑制を2つの場合に限り、設定を緩めて消す手を規約で塞ぎ、再実行で閉じる。この4点を最初に固定すると、指摘が増えても仕分けの基準は変わりません。
同じ型は、他の検査結果にも使えます。依存関係の脆弱性報告を仕分ける例はClaude Codeでnpm auditとpip-auditの結果を仕分けるにあります。基準を文章で渡し、表で確かめ、再実行で閉じる流れは共通です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- docs.platformio.org/en/latest/projectconf/sections/env/options/check/check_too…
- docs.platformio.org/en/latest/core/userguide/cmd_check.html
- docs.platformio.org/en/latest/advanced/static-code-analysis/tools/cppcheck.html
- docs.platformio.org/en/latest/advanced/static-code-analysis/tools/clang-tidy.h…
- docs.platformio.org/en/latest/advanced/static-code-analysis/severity.html
- clang.llvm.org/extra/clang-tidy/index.html