Claude Media
Claude CodeでPlatformIOのpio check指摘を修正と抑制に仕分ける

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つあります。

  1. NOLINTとかっこの間に空白を置くと、チェック名の指定として読まれず、すべての警告を黙らせるNOLINTとして扱われます。チェック名を絞ったつもりが、全部を抑えてしまいます
  2. 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

    修正の行だけ直す

    区分が「修正」の行を、重大度の高い順に直させます。1回の変更に混ぜるのは、同じファイルの指摘までにします。

  2. 2

    抑制の行にコメントを足す

    区分が「抑制」の行に、規約の書式でコメントを足させます。IDとチェック名、理由が揃っているかを差分で確かめます。

  3. 3

    同じ pio check を再実行する

    最初と同じコマンドで回し、修正した分の指摘が消え、新しい指摘が増えていないことを見ます。

  4. 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の結果を仕分けるにあります。基準を文章で渡し、表で確かめ、再実行で閉じる流れは共通です。

参照した一次ソース

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

  1. docs.platformio.org/en/latest/projectconf/sections/env/options/check/check_too…
  2. docs.platformio.org/en/latest/core/userguide/cmd_check.html
  3. docs.platformio.org/en/latest/advanced/static-code-analysis/tools/cppcheck.html
  4. docs.platformio.org/en/latest/advanced/static-code-analysis/tools/clang-tidy.h…
  5. docs.platformio.org/en/latest/advanced/static-code-analysis/severity.html
  6. clang.llvm.org/extra/clang-tidy/index.html
この記事を共有:XはてブLinkedIn