🧳

その神スキル、あなたのマシンでしか動かない。実行時の可搬性をCIで落とすリンタを作った

に公開

「スキル化して」で量産した便利スキル、配ったら自分しか動かなかった

Claude Code や Codex を使っていると、うまくいった手順をそのまま「今のやり方をスキル化して」と頼むだけで、再利用できる SKILL.md が手に入る。私もこれで画像生成やデプロイ手順を次々にスキルにした。快適だった。

つまずいたのは、そのスキルをチームに配ってからだ。私の環境では動くのに、同僚の環境では黙って落ちる。原因を覗くと、だいたい同じだった。

  • 出力先が C:\Users\atlan\Downloads\out.png になっている(私のホームパス)
  • 本文が codex exec ... を呼ぶのに、そのCLIを入れる手順がどこにも書いていない
  • OPENAI_API_KEY が設定済み前提で、未設定時の案内がない
  • gpt-image-2 みたいなモデルIDが直書きされている

どれも「作った本人の環境」が焼き込まれた跡だ。エラーにすらならず、次の人の手元で静かに失敗する。これがいちばんタチが悪い。

この「配ったら動かない」をCIで落とすリンタ、carrylint を作った。

標準は「形式」を可搬にした。「中身が動くか」は別だ

2025年12月、Anthropic は Agent Skills をオープン標準にした。おかげで一つの SKILL.md が Claude Code・Codex・Gemini CLI・Cursor・Copilot など20以上のエージェントで動く。形式の可搬性は、もう標準が解決している。

でも標準が保証するのは器の形だけだ。中に絶対パスや未宣言のCLIが入っていれば、器が正しくても中身は他人の環境で動かない。ここを見ているツールが、探した限り無かった。

  • reflint(既存の自作)は「参照が実在するか」を見る
  • skills-lint(同上)は「スキル同士が衝突しないか・frontmatterが正しいか」を見る
  • carrylint は「参照が別の環境・別のモデルで解決するか」を見る

失敗するクラスが逆だ。他のリンタは「仕様として正しいか」、carrylint は「次の人がインストールして実際に動くか」を見る。

何を検出するか

わざと非可搬にしたサンプル(beku_AI さんの画像生成スキルを"未ハードン化"した例)を通すと、こうなる。

✗ examples/bad/leaky-image-gen/SKILL.md — error 3 / warn 3
  ✗ :16  [abs-path] マシン固有の絶対パス `C:\Users\atlan\Downloads\out.png` — 他人の環境で解決しません
  • :16  [undeclared-cli] `codex` を呼んでいますが、インストール手順も宣言もありません
  ✗ :22  [abs-path] マシン固有の絶対パス `C:\Users\atlan\Downloads\out.png`
  • :25  [provider-env] `OPENAI_API_KEY` を前提にしています
  ✗ :27  [placeholder] 未解決のプレースホルダ `<FILL_ME>` が残っています
  • :29  [todo] TODO/FIXME マーカーが残っています
exit code: 1

ルールは重大度で分けてある。リンタが嫌われる唯一の理由は誤検知だ。だから作者以外は確実に踏む、曖昧さゼロのものだけerror(PRを落とす)にしている。この線引きは公開後の実データ監査で大きく直した(後述)。

重大度 ルール
error 作者環境前提の絶対パス(C:\… /Users/<実名>/)/未完成マーカー(<FILL_ME> REPLACE_ME)。※$HOME~YOUR_API_KEY/path/to/ は可搬 or 文書慣習なので対象外
warn 未宣言の外部CLI(ホストの claude mcp add 等は除外)/プロバイダ固有 env の生参照/TODO: の残り
opt-in モデルID直書き(claude-* gpt-*)※意図的な固定は正当なので既定OFF

実行時にLLMもAPIキーも一切使わない、純粋な静的解析だ。判定基準そのものが「特定の環境・モデルにロックインしていないか」なので、Claude で書いても Codex で書いても同じルールで動く。ツール自体がモデル非依存を体現している。

CIで使う(定着の本体)

name: carrylint
on: [push, pull_request]
jobs:
  carrylint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hyuga611/carrylint@v0

error があればPRにインライン注釈が出てジョブが落ちる。非可搬なスキルはマージできない。人が意識しなくても毎PRで走る——ここが定着の本体だ。手元で試すなら npx @hyuga/carrylint で今すぐ動く。

公開した後、230の実在スキルに当てて、自分が85%誤検知していると知った

ここが一番正直に書きたいところだ。公開したはいいが、宣伝する前に一つ引っかかった。自分で作った examples と自分で書いたテストが通るのは当たり前で、正しさの証明にはならない。 だから、GitHubの公開リポから実在の SKILL.md / AGENTS.md230件集めて、carrylint をそのまま当ててみた。

良い半分。作者しか動かないスキルは、本当にあった。 あるPPT生成スキルは本文に /Users/guohao/Documents/... と作者のMacパスを直書きし、別のスキルは C:/Users/vudrk/Desktop/AI Projects/ を全スクリプトの基準にしていた。どれもエラーにすらならず、次の人の手元で静かに落ちる。carrylint が存在する理由は、実データにあった。

まずい半分。でも、エラーの約85%は私の誤検知だった。 Bearer YOUR_API_KEY(APIドキュメントの「ここに鍵を入れてね」という慣習)を「未完成」と誤検知し、claude mcp add(ホスト自身)を「未宣言のCLI」と誤検知し、$HOME/...(各ユーザーで解決する可搬な書き方)を「マシン固有の絶対パス」と誤検知していた。誤検知はリンタの唯一の死因だと自分で書いておいて、宣伝する前にそれを浴びるところだった。

幸い、実データが「どこを直すか」を名指ししていた。v0.1.1 で4点直した——$HOME/~/汎用名は可搬扱いに、プレースホルダは埋め忘れマーカーだけに、ホストCLIの設定コマンド(claude mcp add 等)は除外、ホーム相対パスのルールは廃止。同じ230件に当て直すと、ERRORの誤検知は約85%→ほぼ0%、本物は全部残った。 さらに一度も見ていない別の70リポで当て直し、過学習でないことも確かめた(発火3%・全部本物)。見つけた実例は回帰テストとしてリポに同梱してある。

教訓はひとつ。自作テストが通ることは正しさの証明にならない。実データに当てて、しかも宣伝する前に当てて、初めて分かる。

正直な話:このニッチはもう混雑している

もう一つ本音を。設計の途中で調べて気づいたのだが、SKILL.md を検査するリンタは2026年時点で既に7本以上あった。私の skills-lint もその一つだ。「まっさらな空き地」ではなかった。

ただ、それらを全部読んで確認した。「中身が実際に他環境で動くか」を見ているものは、一つも無かった。 みんな仕様準拠と frontmatter で止まっている。だから carrylint はそこ一本に絞った。Claude と Codex を混ぜて使い、かつスキルを配布共有するチームは、正直まだ多くない。需要は少し先回りかもしれない。それでも「配ったら自分しか動かなかった」に一度でも刺さった人には、(監査で誤検知を削り込んだ)低ノイズなERRORルールが効くはずだ。

まとめ

標準は SKILL.md を「形式として」可搬にした。だが「実際に動くか」は別の話だ。carrylint はそこをCIで落とす。

  • npx @hyuga/carrylint で今すぐ試せる
  • GitHub Action なら uses: hyuga611/carrylint@v0 の一行

リポジトリはこちら。 https://github.com/hyuga611/carrylint

GitHubで編集を提案

Discussion