CLAUDE LABEN
FABLE — Claude Fable 5 のサブスク提供が確定しました。Max と Team Premium は週次利用上限の50%までを含む形で継続し、Pro と Team Standard は従量クレジットへ移りますSKILLS — /help や /feedback といったターミナル組み込みコマンドと同名のスキルが、非対話セッションで呼び出せなくなる不具合が修正されましたMCPINIT — initialize 制御リクエストで登録した SDK の MCP サーバが、次のターンを待たずに即座に接続を始めるようになりましたRESYNC — セッション途中の MCP 再同期でプラグイン提供のサーバが落とされる問題と、2つのプロセスが同じ OAuth トークンを同時更新して再認証を強いる競合が直りましたSTREAM — --forward-subagent-text と CLAUDE_CODE_FORWARD_SUBAGENT_TEXT で、サブエージェントのテキストと思考を stream-json 出力に含められますPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までです。9月1日から $3/$15 per Mtok に戻りますFABLE — Claude Fable 5 のサブスク提供が確定しました。Max と Team Premium は週次利用上限の50%までを含む形で継続し、Pro と Team Standard は従量クレジットへ移りますSKILLS — /help や /feedback といったターミナル組み込みコマンドと同名のスキルが、非対話セッションで呼び出せなくなる不具合が修正されましたMCPINIT — initialize 制御リクエストで登録した SDK の MCP サーバが、次のターンを待たずに即座に接続を始めるようになりましたRESYNC — セッション途中の MCP 再同期でプラグイン提供のサーバが落とされる問題と、2つのプロセスが同じ OAuth トークンを同時更新して再認証を強いる競合が直りましたSTREAM — --forward-subagent-text と CLAUDE_CODE_FORWARD_SUBAGENT_TEXT で、サブエージェントのテキストと思考を stream-json 出力に含められますPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までです。9月1日から $3/$15 per Mtok に戻ります
記事一覧/Claude Code
Claude Code/2026-08-06上級

呼んだはずのスキルが何も返さない — 起動前に名前の解決順序を確定させる

端末の組み込みコマンドと同名のスキルは、非対話セッションで呼び出しが解決されないことがあります。実効名の表を起動前に作る preflight を書いて走らせ、検査器自身が取りこぼしていた2件までを実測で洗い出した記録です。

Claude Code212スキル設計無人実行4preflight4名前空間2

プレミアム記事

深夜に回しているスケジュール実行のログを朝に開いたら、進行の記録がほとんど残っていませんでした。

失敗した形跡はありません。エラーも、スタックトレースも、タイムアウトの記録もない。ただ、呼んだはずの手順が一度も動いていない。

原因にたどり着くまでに、私は権限設定とネットワークの許可リストを先に疑いました。どちらも無実でした。実際に起きていたのは、もっと素朴なことです。プラグインから配布していたスキルの名前が、端末の組み込みコマンドと衝突していました。

対話セッションでは、同じ名前を打つと組み込み側が応答します。ですから「動いている」ように見えます。非対話セッションでは、その呼び出しが誰にも解決されないまま、静かに落ちていました。

この不具合そのものは 2026年8月6日付の更新で修正されています。ただ、修正されたのは特定の症状であって、構造は残ります。呼び出し名は複数のソースから供給され、実効名は解決順序で決まる。その順序を知らないまま名前を付けると、また同じ形で静かに壊れます。

そこで、実効名の表を起動前に作る道具を書きました。書いて、走らせて、その検査器自身が取りこぼしていたものを見つけました。

呼び出し名は一覧ではなく解決順序で決まる

スキルの供給元は一つではありません。プロジェクト直下、ユーザーのホーム、プラグイン経由の配布。組織で運用していれば、そこに管理者が配る束が加わります。

私たちはこれを「使えるスキルの一覧」として捉えがちです。実際には一覧ではなく、同じ名前空間に複数のソースが書き込む多重定義です。

供給元置き場所誰が変更するか変更に気づけるか
組み込みコマンド端末本体ベンダー更新履歴を読めば
プロジェクトリポジトリ内自分・チーム差分に出る
ユーザーホーム配下自分版管理外なら気づけない
プラグイン導入したプラグイン配布元ほぼ気づけない

厄介なのは下の2行です。ホーム配下のスキルはリポジトリの差分に出ません。プラグインが提供するスキルは、配布元が新しい名前を追加した瞬間に、こちらの断りなく名前空間へ入ってきます。

つまり、自分の書いたスキルが今日も同じ名前で呼べるかどうかは、自分だけでは決められません。

ここに組み込みコマンドが加わります。組み込み側は最も強い。そして最も入れ替わりが速い。8月に入ってからだけでも、手動で起動できるスキルの口が増えています。昨日まで空いていた名前が、今日は埋まっている。そういう性質のものです。

対話と非対話で挙動が分かれる理由

対話セッションでは、入力した名前は端末の入力層をまず通ります。組み込みコマンドと一致すれば、そこで処理が終わります。人間が打っている以上、応答がないことにはすぐ気づきます。

非対話セッションには、その入力層がありません。呼び出しはエージェント側の解決に委ねられます。ここで名前が組み込み側に予約されていると、エージェントの持つスキル表とベンダーの予約語がずれ、呼び出しが宙に浮きます。

そして無人実行には、宙に浮いたことを咎める人がいません。

私自身、この構造を理解するまで「権限が足りていないはずだ」という筋で3時間ほど調べていました。権限の問題であれば拒否の記録が残ります。何も記録が残っていないという事実こそが、権限ではないという手がかりだったのですが、そこに気づくのが遅れました。

実効名の表を起動前に作る

必要なのは、実行のたびに「この名前を呼んだら、どのファイルが動くのか」を確定させることです。

方針は3つに絞りました。

  1. すべての供給元を走査し、実効名を1つの表に集約する
  2. 組み込みコマンドと衝突する名前は、警告ではなく停止として扱う
  3. 走査できなかった供給元があれば、それ自体を失敗として報告する

3つ目が後で効いてきます。

以下がそのまま動く実装です。Node.js の標準モジュールだけで完結させました。依存を足すと、無人実行の環境で入っていない事故が起きるためです。

#!/usr/bin/env node
// name-preflight.mjs — スキルの「呼び出し名」を起動前に解決し、衝突を落とす
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
import { join, basename } from "node:path";
 
// 端末組み込みコマンド。自分の環境の /help 出力から起こし、CI では固定リストとして版管理する
const BUILTIN = new Set([
  "help", "clear", "compact", "config", "cost", "doctor", "exit",
  "init", "login", "logout", "mcp", "memory", "model", "permissions",
  "release-notes", "resume", "review", "rewind", "status", "vim",
]);
 
// 優先度: 数字が小さいほど先に解決される想定。実環境の観測結果で必ず上書きすること
const SOURCES = [
  { id: "project", rank: 1, root: ".claude/skills" },
  { id: "user",    rank: 2, root: join(process.env.HOME ?? "", ".claude/skills") },
  { id: "plugin",  rank: 3, root: ".claude/plugins", nested: true },
];
 
const missingRoots = [];
 
function readName(dir) {
  const f = join(dir, "SKILL.md");
  if (!existsSync(f)) return null;
  const head = readFileSync(f, "utf8").split(/\n---\s*\n/)[0];
  const m = head.match(/^name:\s*(.+)$/m);
  return (m ? m[1] : basename(dir)).trim().replace(/^["']|["']$/g, "");
}
 
function scanFlat(root, source, owner) {
  if (!existsSync(root)) { missingRoots.push(`${source}:${root}`); return []; }
  return readdirSync(root)
    .map((e) => join(root, e))
    .filter((p) => statSync(p).isDirectory())
    .map((p) => ({ name: readName(p), source, owner, path: p }))
    .filter((r) => r.name);
}
 
function collect() {
  const out = [];
  for (const s of SOURCES) {
    if (!s.nested) { out.push(...scanFlat(s.root, s.id, s.id)); continue; }
    if (!existsSync(s.root)) { missingRoots.push(`${s.id}:${s.root}`); continue; }
    for (const plug of readdirSync(s.root)) {
      out.push(...scanFlat(join(s.root, plug, "skills"), s.id, plug));
    }
  }
  return out;
}
 
function resolve(entries) {
  const rank = Object.fromEntries(SOURCES.map((s) => [s.id, s.rank]));
  const byName = new Map();
  for (const e of entries) {
    const list = byName.get(e.name) ?? [];
    list.push(e);
    byName.set(e.name, list);
  }
  const findings = [];
  for (const [name, list] of byName) {
    list.sort((a, b) => rank[a.source] - rank[b.source]);
    const winner = list[0];
    if (BUILTIN.has(name)) {
      findings.push({
        level: "block", name,
        reason: "端末組み込みコマンドと同名。非対話セッションでは呼び出しが解決されない可能性がある",
        effective: "builtin", losers: list,
      });
      continue;
    }
    if (list.length > 1) {
      findings.push({
        level: "warn", name,
        reason: `同名スキルが ${list.length} 件。${winner.source}(${winner.owner}) が優先される`,
        effective: `${winner.source}:${winner.owner}`, losers: list.slice(1),
      });
    }
  }
  return { total: entries.length, unique: byName.size, findings };
}
 
const t0 = process.hrtime.bigint();
const r = resolve(collect());
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
 
const blocks = r.findings.filter((f) => f.level === "block");
const warns  = r.findings.filter((f) => f.level === "warn");
for (const m of missingRoots) console.log(`[GAP] 走査対象が存在しません: ${m}`);
console.log(`scanned=${r.total} unique=${r.unique} block=${blocks.length} warn=${warns.length} in ${ms.toFixed(1)}ms`);
for (const f of [...blocks, ...warns]) {
  console.log(`[${f.level.toUpperCase()}] /${f.name} -> ${f.effective}`);
  console.log(`        ${f.reason}`);
  for (const l of f.losers) console.log(`        shadowed: ${l.source}:${l.owner} (${l.path})`);
}
process.exit(blocks.length > 0 || missingRoots.length > 0 ? 1 : 0);

BUILTIN を定数で持たせている点には理由があります。組み込みコマンドの一覧をその場で問い合わせて取ると、問い合わせ自体が失敗したときに「衝突ゼロ」という誤った安心を返してしまいます。手元で確認した一覧を版管理に置き、更新のたびに差分を目で見る。この鈍い運用のほうが、無人実行では信頼できます。

rank の並びも同様です。優先順位を推測で決め打ちしても、実環境がその通りとは限りません。ここは自分の環境で意図的に衝突を作り、どちらが応答したかを観測して上書きしてください。私が意味を持たせているのは「順序が固定されていて、その順序が明示されている」という一点です。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
組み込みコマンドと同名のスキルを起動前に落とす preflight(Node.js 約90行・そのまま動きます)と、10件のスキルツリーでの実測出力
検査器の走査対象が1つ欠けるだけで検出が4件から5件へ変わる構造と、走査漏れを fail-loud にする実装
ディレクトリ名で数えると1件、実効名で数えると2件になる理由と、500スキル18ms という走査コストの実測
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

Claude Code2026-07-18
宣言したMCPサーバーが一覧から消えていた — 名前空間をベンダーと共有する前提で設定を組み直す
MCPサーバー名がベンダーに予約され、自分の .mcp.json の宣言が黙って無効になる。無人実行で気づけないこの欠落を、宣言と実体を突き合わせる preflight と接頭辞規約で落とす設計をまとめました。
Claude Code2026-07-29
MCPサーバーは繋がっているのに、ツールが0件で返る — 無人実行の権限劣化をツール表の差分で止める
資格情報が抜けてもMCPのハンドシェイクは成功し、tools/list は空配列を返します。例外が飛ばないまま実行が完走する構造と、期待ツール表との差分で無人実行を止めるpreflightの実装を、実測値つきで記録しました。
Claude Code2026-07-15
無人実行のログを読める形に保つ — axScreenReader のプレーンテキスト描画を計測して常用に切り替える
Claude Code の出力をログへ落とすと、制御列と再描画で膨らみ後から追えなくなります。axScreenReader のプレーンテキスト描画を3経路で有効化し、同一タスクを計測して常用へ切り替えるまでの実装と判断を記録します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →