SpyBara
Go Premium

plugins/mods/api.md 2026-11-11 22:59 UTC to 2026-11-12 23:02 UTC

No changes for this document in this history range.

2026
Mon 2 23:57

mods API を使用する

Claude Code mod から mods API を呼び出して、コマンドとツールを追加し、モデルを呼び出し、タイマーで作業を実行し、他のセッションにメッセージを送信し、ファイルとネットワークにアクセスします。

mods API は、mod が動作するために呼び出すメソッドのセットです。コマンドとツールを追加し、モデルを呼び出し、イベント間で作業を実行し、ファイルシステム、プロセス、ネットワークにアクセスします。すべてのフックは、最初の引数として $ を受け取り、メソッドは $.ui や $.fs などの名前空間でグループ化されています。Events はフックが実行されるタイミングを決定し、mods API はフックが実行されたときに呼び出すものです。

最初の mod を作成 してからここを開始してください。すべてのメソッドについては、mods API メソッド を参照するか、ビルド用の型 を読んでください。

コマンドまたはツールを追加する

mod はユーザーが実行するコマンドと Claude が呼び出すツールを追加できます。両方を session.start フックに登録します。Claude Code はそのフックを最初のプロンプトの前に待つため、登録したものは最初のターンから利用可能です。

コマンドを追加する

コマンドはユーザー向けです。登録してから、その名前の command.run を処理します。この例は、オプションの日数を取る /standup コマンドを追加します。

on('session.start', async ($, e, next) => {
  // /standup をコマンドリストに追加し、ユーザーがそこで見る説明を付ける
  await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
  return next(e)
})

// マッチャーはフックを /standup に制限するため、他のコマンドは到達しない
on('command.run', { command: 'standup' }, async ($, e) => {
  // e.args はコマンド名の後に入力されたテキスト、または空の文字列
  return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
})

セッションが開始した後、/standup はその説明とともに / を入力したときに表示されるリストに表示されます。argumentHint は、コマンドを入力してスペースを入力した後、プロンプトに /standup [days] として表示されます。/standup 3 を実行すると、2 番目のフックは Summary for the last 3 day(s): ... を返し、トランスクリプトはプラグイン名の後にそのテキストを表示します。フックは next を呼び出しません。コマンドはあなたのもの以外に動作がないためです。

返す text はトランスクリプトに出力され、Claude がそれを読みます。何も出力しない場合、ペイン を開くだけのコマンドの場合は、{} を返します。Claude が作業中にコマンドを実行できるようにするには、登録に immediate: true を追加します。

組み込みコマンドが使用していない名前を選択してください。セッションで / を入力して、それらを確認してください。$.command.register は、"/focus" refused: it is the built-in /focus などのメッセージで、取得された名前に対してスローします。スローするフックはスキップされるため、その session.start フックの残りも実行されません。そのフックの最後にコマンドを登録するか、呼び出しを try と catch でラップします。

ツールを追加する

ツールは Claude 向けです。名前、Claude が読む説明、入力用の JSON Schema で登録します。Claude は、mcp__、プラグイン名、2 つのアンダースコア、登録した名前で構成される長い名前の下にそれを見ます。その呼び出しを、その完全な名前にフィルタリングされた tool.call フックで処理します。この例は、my-mod という名前のプラグインから、ticket を登録するため、完全な名前は mcp__my-mod__ticket です。Claude に問題追跡ツールでチケットを検索するツールを提供します。

on('session.start', async ($, e, next) => {
  await $.tool.register({
    name: 'ticket',
    // Claude はこの説明からツールを呼び出すタイミングを決定する
    description: 'Look up a ticket by its id and return its title and status',
    // Claude が送信する必要がある引数:id という名前の 1 つの必須文字列
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  })
  return next(e)
})

// 完全なツール名は mcp__、プラグイン名、登録された名前
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
  // ツールの引数は e のフィールドなので、id は e.id
  const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
  // どちらの場合でも結果を返すため、Claude は検索が失敗したときに学習する
  return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
})

チケットについて質問すると、Claude は mcp__my-mod__ticket をその id で呼び出すことができます。2 番目のフックはチケットを取得し、応答本文を返します。Claude はそれをツールの結果として読みます。サーバーがエラーステータスで応答すると、Claude は Lookup failed with status と数字を読みます。

[!TIP]

MCP ツール検索が登録済みのツールを遅延させると、Claude はそのツールを検索するまで、名前は見えますが説明は見えません。Claude が毎ターンそのツールを検討すべき場合は、登録に isDeferred: false を追加して、ツール全体を事前に読み込みます。このフィールドには Claude Code v2.1.293 以降が必要で、それより前のバージョンでは無視されます。

モデルを呼び出す

mod は、テキストの分類や要約などの小さなジョブのために、独自のリクエストをモデルに送信できます。$.model.complete はプロンプトを単独で送信し、$.model.fork({ prompt }) は現在の会話の末尾にプロンプトを付けて送信します。

次の表は、それぞれのリクエストに含まれる内容を比較したものです。

リクエストの内容 $.model.complete $.model.fork
モデル 渡した model セッションのモデル
システムプロンプト 短い帰属ブロック、続いて system を渡した場合はその内容 セッションのシステムプロンプト
メッセージ 1 つのユーザーメッセージ(渡した prompt) これまでの会話、続いてユーザーメッセージとしての prompt
CLAUDE.md およびその他のプロジェクトコンテキスト 含まれない 会話の最後のリクエストと同様に含まれる
ツール なし Claude のツール(モデルは呼び出せない)

フォークは会話の最後のリクエストを繰り返すため、会話がまだキャッシュされている間は、Claude API はその大部分をプロンプトキャッシュから提供します。

どちらの呼び出しもセッションの認証情報を使用するため、ユーザーのプラン、API キー、またはクラウドプロバイダーに課金されます。ビルド用の型には、すべての $.model メソッドが記載されています。

1 つのプロンプトを送信する

$.model.complete に model と prompt を渡します。prompt はユーザーメッセージになります。役割や出力形式などの指示をモデルに与えるには、system も渡します。これはシステムプロンプトになります。

このフックは、コマンドとして登録された /triage コマンドに答え、小さなモデルに、その後に入力されたテキストにラベルを付けるよう尋ねます。

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    // システムプロンプトはジョブを設定し、プロンプトはラベル付けするテキストを運ぶ
    system: 'Reply with one word: bug, feature, or question.',
    prompt: e.args,
    // 1 語は少数のトークンが必要で、呼び出しは 15 秒後にあきらめる
    maxTokens: 20,
    timeoutMs: 15000,
  })
  // r.text は、モデルが応答したときのみ存在するため、最初に r.isAnswered をチェック
  const label = r.isAnswered ? r.text.trim() : 'unknown'
  return { text: 'Label: ' + label }
})

/triage the export button does nothing を実行すると、mod はそのテキストをモデルに送信し、その答え(Label: bug など)を出力します。モデルが応答しない場合、ラベルは unknown です。

Claude API の失敗は呼び出しを拒否しないため、r.isAnswered をチェックし、それが false の場合は r.reason を読んでください。呼び出しは、Claude Code が送信しないリクエスト(組織がブロックするモデルなど)に対して拒否されます。

ビルド用の型には effort などの他のオプションが記載されており、制限には maxTokens のデフォルトが示されています。

プロンプトキャッシュを使用する

$.model.complete は Claude API のプロンプトキャッシュをサポートしています。API は、リクエストの先頭部分(プレフィックスと呼ばれます)を、設定したキャッシュブレークポイントまでキャッシュします。すべての呼び出しが、指示や参考資料などの同じ長い静的コンテンツで始まる場合は、そのコンテンツの末尾にブレークポイントを設定します。以降の呼び出しでは、その部分に対して入力の全額を支払う代わりに、キャッシュから読み取ります。

ブレークポイントを設定するには、prompt を文字列ではなく { text } ブロックの配列として渡し、静的コンテンツの最後のブロックに cache: true を追加します。Claude Code はそのブロックを API の cache_control フィールド付きで送信します。system も同じ配列形式を受け付けます。どちらを使うかの判断については、prompt と system のどちらを使うか選ぶを参照してください。

[!NOTE]

ブロックの配列には Claude Code v2.1.292 以降が必要です。それより前のバージョンでは、prompt 内の配列は takes { model, prompt } (host check) で終わるエラーで拒否され、system 内の配列はリクエストから除外されます。

このバージョンの /triage フックは、ラベル付けするテキストの前に長いラベル付けルールを送信し、ルールの後にブレークポイントを置きます。RULES は独自に用意する文字列です。

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    prompt: [
      // すべての呼び出しで同一なので、キャッシュされるプレフィックスになる
      { text: RULES, cache: true },
      // 呼び出しごとに変わるので、ブレークポイントの後に置く
      { text: e.args },
    ],
  })
  return { text: 'Label: ' + (r.isAnswered ? r.text.trim() : 'unknown') }
})

TTL とブレークポイントの数には次の制限があります。

  • TTL: キャッシュエントリは最後に使用されてから 5 分間保持されます。TTL は呼び出しではなく、ユーザーの Claude Code 設定によって決まります。1 時間にするには、subagentPromptCacheTtl を 1h に設定します。
  • リクエストあたりのブレークポイント数: API は最大 4 つまで受け付け、それを超えると r.reason に api-error が返されます

prompt と system のどちらを使うか選ぶ

リクエストが Claude API に直接送られることがわかっている場合を除き、呼び出し間で共有する静的コンテンツは prompt の先頭に置いてください。

  • API キーまたは Claude のサブスクリプションで Claude API に直接送る場合: どちらのフィールドでも機能します
  • Amazon Bedrock、Claude Platform on AWS、Google Cloud の Agent Platform、Microsoft Foundry、または LLM ゲートウェイを経由する場合: prompt を使用します。Claude Code はシステムプロンプトの先頭に帰属ブロックを置き、そのフィンガープリントはユーザーメッセージの先頭から生成されます。api.anthropic.com エンドポイントはキャッシュの前にそのブロックを取り除きます。その他のエンドポイントはそれをプロンプトの一部として受け取るため、prompt の先頭が異なると system 内のブレークポイントがヒットしないことがあります。
  • 他の人が実行する mod の場合: prompt を使用します。相手のプロバイダーは選べないためです

プレフィックスでは system が prompt より前に来るため、prompt 内のブレークポイントは system もカバーし、system が異なる呼び出しはキャッシュにヒットしません。

キャッシュヒットを確認する

$.model.complete の結果には、API のキャッシュフィールドを含む usage オブジェクトがあります。usage.cache_creation_input_tokens は呼び出しがキャッシュに書き込んだトークン数を、usage.cache_read_input_tokens はキャッシュから読み取ったトークン数を数えます。最初の呼び出しでは書き込みが発生し、TTL 内の以降の呼び出しでは読み取りが発生するはずです。

すべての呼び出しで書き込みが発生し読み取りがない場合は、呼び出し間でプレフィックスが異なっているか、呼び出しの間隔が TTL より長くなっています。プレフィックスが異なる場合については、prompt と system のどちらを使うか選ぶを参照してください。

モデルが応答した呼び出しで両方のフィールドがゼロのままの場合は、何もキャッシュされていません。次の各原因を確認してください。

  • プレフィックスが短すぎる: API はモデルの最小長未満のプレフィックスをキャッシュせず、エラーも返しません
  • プロンプトキャッシュが無効になっている: DISABLE_PROMPT_CACHING 変数がそのモデルに適用されている場合、Claude Code はブレークポイントを削除し、テキストをキャッシュなしで送信します
  • ゲートウェイが cache_control を取り除いている: ゲートウェイはフィールドを削除しても成功を返すことがあります
  • 別の mod がテキストの先頭を書き換えている: その場合、Claude Code はブレークポイントなしで送信します

model.complete フックが受け取るもの

model.complete イベントをフックして他の mod のリクエストを検査または変更する場合は、次のフィールドからテキストを読み取ります。

  • e.prompt: 常に文字列です。呼び出し元が配列を渡した場合は、ブロックのテキストを順に連結したものです。
  • e.system: 同じ方法で構築された文字列です。呼び出し元が system を渡さなかった場合は存在しません
  • e.promptBlocks と e.systemBlocks: 呼び出し元の配列です。それぞれ、呼び出し元がそのフィールドに配列を渡した場合に存在します

Claude Code は、フックが next に渡した文字列を送信し、それと一緒に渡された配列を使用してキャッシュブレークポイントを配置します。文字列の先頭とまだ一致している先頭のブロックはブレークポイントとともに保持し、文字列の残りはブレークポイントなしで送信します。たとえば、next({ ...e, prompt: e.prompt + NOTE }) は呼び出し元のブレークポイントを保持し、prompt の先頭を変更するフックはそれらを削除します。

バックグラウンドで作業を実行する

1 つのイベントを超える作業(1 分ごとに何かをチェックするなど)は、session.start から開始するタイマーで実行されます。フック自体は 1 つのイベントに対して実行され、自身の実行時間には時間制限があります。next または mods API 呼び出しで費やされた時間はカウントされません。ただし、$.clock.sleep は例外です。$.clock.every と $.clock.after は setInterval と setTimeout の代わりになり、遅延はミリ秒で最初に来ます。$.clock.after(5000, fn) は fn を 1 回呼び出し、今から 5 秒後です。各々はタイマーを返し、cancel() メソッドを持ち、await $.clock.now() はミリ秒単位の時間を与えます。

このフックはプルリクエストのチェックを 1 分ごとに検索し、プロンプトの下に結果を表示します。summarize はコマンドの JSON 出力を数語に変換する独自の関数です。

on('session.start', async ($, e, next) => {
  // 60,000 ミリ秒ごとに関数を呼び出し、今から 1 分後に開始
  $.clock.every(60_000, async () => {
    const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
    // プロンプトの下の行を最新の要約に置き換える
    $.ui.status('checks: ' + summarize(status.stdout))
  })
  // タイマーを待たずに戻るため、セッションはすぐに開始される
  return next(e)
})

セッションは通常どおり開始されます。1 分後、プロンプトの下に ⚠、mod の名前、その後 checks: とあなたの要約を含む行が表示されます。その後、1 分ごとに置き換えられます。タイマーのコールバックはイベント外で実行されるため、ターン間で実行され続け、ターンを開始しません。コールバックがスローする場合、エラーは デバッグログ に移動し、タイマーは次の間隔で再度実行されます。

ターンを開始せずに何かを表示する

バックグラウンドジョブは、ターンを開始せずにユーザーに何かを表示できます。これらの各呼び出しは、異なる場所にテキストを配置します。

呼び出し ユーザーが見るもの
$.ui.status(text) プロンプトの下の 1 行で、変更するまで残ります。⚠ と mod の名前で始まります。⚠ my-mod: checks: 3 passing など。
$.ui.toast(text) mod の名前を含むトースト通知で、数秒後に消えます。フルスクリーンレンダリングでは右上のボックスとして、クラシックレンダラーではプロンプトの下の右側に 1 行で表示されます。
$.ui.log(text) Claude が読まない、トランスクリプト内の薄い行。● と mod の名前で始まります。● my-mod: build finished など。
$.ui.notify(text) Claude Code の外部で、ユーザーのターミナルから送られる通知で、ユーザーの preferredNotifChannel 設定に従って送信されます。空でない { title } を渡さない限り mod の名前がタイトルになり、渡した場合は指定したとおりに表示されます。

これらのうち応答を返すのは $.ui.notify だけです。ユーザーのチャネルが通知をターミナルに書き込んだ場合、{ isSent: true, channel } で解決します。channel は通知を送信した設定の値で、kitty や terminal_bell などです。terminal_bell チャネルはベルを鳴らし、テキストもタイトルも表示しません。

それ以外の場合、呼び出しは { isSent: false, reason } で解決します。

reason 意味
disabled ユーザーが preferredNotifChannel を notifications_disabled に設定している
no-channel 設定が auto で、ユーザーのターミナルに Claude Code が送信できる通知がない
no-surface セッションにターミナルがない(-p 実行、Agent SDK、デスクトップアプリ、VS Code 拡張機能、クラウドセッションなど)。セッション開始前の mod の読み込み中に行われた呼び出しや、Claude Code の終了中に行われた呼び出しに対する応答でもあります
refused ui.notify に対する別の mod のフックが、送信せずに Claude Code の代わりに応答した

text が文字列でない場合、ui.notify に対する別の mod のフックが { deny } を返した場合、またはターミナルへの書き込みに失敗した場合、呼び出しは解決せずに reject します。

mod はチャネルを選択できず、Claude Code は mod が通知する頻度を制限しません。ユーザーの設定が唯一のスイッチです。

呼び出しが実行中のセッションに届くと、ユーザーの Notification フックがタイプ plugin_notification で実行されます。これらは応答が disabled の場合や、応答が no-surface となるターミナルのないセッションでも実行されます。セッション開始前や Claude Code の終了中に行われた呼び出しでは、フックは実行されません。mod が $.ui.notify を呼び出したフックはその実行から除外されるため、通知を行う classic.Notification フックが自身の通知に対して再度呼び出されることはありません。

ターミナルは通知を表示したかどうかを報告しないため、isSent: true は通知が送信されたことを意味し、ユーザーがそれを見たことを意味するわけではありません。

この例では、メインの会話のターンが中断されずに終了したときに通知し、テキスト付きの通知が送信されなかった場合はトーストにフォールバックします。ユーザーの Notification フックが先に実行され、時間がかかることがあるため、イベントを次に渡す前に通知を待ちません。

on('turn.complete', ($, e, next) => {
  const isMainTurn = e.agentId === undefined && !e.isAborted
  const fallBack = () => $.ui.toast('Turn finished')
  if (isMainTurn) {
    $.ui.notify('Turn finished').then(sent => {
      const hasText = sent.isSent && sent.channel !== 'terminal_bell'
      if (!hasText) fallBack()
    }, fallBack)
  }
  return next(e)
})

VS Code 拡張機能では、ステータスラインとトーストは代わりにプロンプトボックスの上に表示され、ログ行は会話内に表示されます。

バックグラウンドジョブからターンを開始する

バックグラウンドジョブが Claude の注意が必要なものを見つけた場合、$.prompt.submit({ text }) でプロンプトを送信してターンを開始できます。Claude は、送信者として mod に名前を付ける文の後にテキストを読みます。ユーザー自身の言葉として送信するには、その文なしで、asUser: true を追加します。呼び出しはセッションがアイドル状態になるまで待機してから、新しいターンを開始します。そのターンが開始されたときに解決するため、Claude が作業中に実行されるハンドラーで await しないでください。

バックグラウンド作業を停止する

タイマーはモジュールの再読み込み後に停止します。フック内で長時間実行される処理には、next.signal を使用します。これは AbortSignal であり、フックが処理しているイベントが破棄されたとき(たとえばユーザーが中断したとき)に中止されます。長時間実行されるものにはすべてこれを渡してください。

ユーザーがターンを中断した後の呼び出しとバックグラウンド作業

結果またはエラーに含まれる理由 the session was stopped and nobody has sent input since は、ユーザーが Esc や Stop などで実行中のターンを停止したことを意味します。この中断の後、セッションで新しい作業を開始する呼び出しは、プロンプトでのメッセージなど、ユーザーの次の入力があるまで拒否されます。

$.prompt.submit と $.command.run を除き、中断後の呼び出しとバックグラウンド作業に関するこの処理には Claude Code v2.1.304 以降が必要です。

拒否またはドロップされた呼び出しを再試行する

拒否またはドロップされた呼び出しは、turn.complete フックから再度実行します。呼び出しはユーザーの次の入力まで拒否され続け、それらを自動的にキューに入れたり繰り返したりする仕組みはありません。

呼び出しの結果またはエラーに次のいずれかの文字列が含まれる場合は、後で再試行するためにその引数を保持します。

  • the session was stopped and nobody has sent input since: 呼び出しが拒否された
  • was removed from the queue before it ran: プロンプトまたはコマンドが実行されないままキューから外れ、ドロップされた(たとえばユーザーがターンを停止した場合)

この例では、バックグラウンドジョブから $.prompt.submit の代わりに submitPrompt を呼び出します。pendingText 変数と submitPrompt 関数は register の上に置き、turn.complete フックはその内側に置きます。検証では、register 内で宣言された関数に渡された $ が拒否されるためです。結果またはエラーにこれらの文字列のいずれかが含まれる場合、submitPrompt は turn.complete フックが再送信できるように、テキストを pendingText に保持します。

// 最後に拒否またはドロップされたプロンプトのテキスト、または null
let pendingText = null

async function submitPrompt($, text) {
  let reason
  try {
    const result = await $.prompt.submit({ text })
    reason = result?.drop ?? ''
  } catch (error) {
    reason = String(error?.message ?? error)
  }
  if (reason.includes('the session was stopped and nobody has sent input since') ||
      reason.includes('was removed from the queue before it ran')) {
    pendingText = text
  }
  // それ以外の結果やエラーは再試行しない
}

on('turn.complete', async ($, e, next) => {
  if (pendingText !== null) {
    const text = pendingText
    // 再試行の前に pendingText を空にし、このフックが再度実行されても
    // 同じテキストを送信しないようにする。
    pendingText = null
    // await しない: 新しいターンが開始されるまでフックが開いたままになるため
    submitPrompt($, text)
  }
  return next(e)
})

ユーザーの次の入力より前に、中断されたターン自体やサブエージェントのターンに対して turn.complete が発火することがあり、そのときに行われた呼び出しは再び拒否されます。その場合、submitPrompt は後の turn.complete のためにテキストを保持します。

$.command.run のように reject する呼び出しでは、エラーをキャッチし、submitPrompt が reason を調べるのと同じ方法でそのメッセージを調べます。

拒否された呼び出しが返すもの

結果内の <reason> は、呼び出しが拒否された理由を表します。中断後の場合、それは the session was stopped and nobody has sent input since です。その他の理由については、claude -p 実行と Agent SDK セッションで拒否される呼び出しを参照してください。

拒否される呼び出しの例 結果
$.prompt.submit { drop: '<reason>' } で解決し、プロンプトをキューに入れません
$.command.run メッセージが <reason> で終わるエラーで reject します
$.agent.spawn { deny: '<reason>' } で解決し、サブエージェントを開始しません
終了したサブエージェントへの $.session.send { isDelivered: false, reason: 'Agent "<id>" was not resumed: <reason>.' } で解決します。<id> はサブエージェントの ID です。まだ実行中のサブエージェントはメッセージを受け取ります。
エージェントチームのチームメイトへの $.session.send { isDelivered: false, reason: 'Teammate "<name>" was not messaged: <reason>.' } で解決します。<name> はチームメイトの名前です
run_in_background: true を指定した Bash、Monitor、または CronCreate の $.tool.call { deny: '<tool> was not run: it starts background work, a schedule or a goal, and <reason>' } で解決します。<tool> はツールの名前です

対話型セッションで中断後にこれらの $.session.send または $.tool.call の呼び出しが拒否された場合、ユーザーには Stopped: トーストが表示されることがあります。

中断前に mod が開始した作業

ユーザーが Esc などで実行中のターンを停止した後、mod がすでに開始していた作業の一部はドロップされ、一部はユーザーの次の入力を待ちます。

作業 ユーザーの次の入力までに起こること 対処方法
まだキューにある $.prompt.submit からのプロンプト ドロップされます。呼び出しは { drop: 'the prompt was removed from the queue before it ran' } で解決します。 拒否またはドロップされた呼び出しを再試行するの方法で再送信します
まだキューにある $.command.run からのコマンド ドロップされます。呼び出しは、メッセージに the command was removed from the queue before it ran を含むエラーで reject します。 拒否またはドロップされた呼び出しを再試行するの方法で再実行します
CronCreate などのツールでスケジュールされた 1 回限りのジョブで、ユーザーの次の入力より前に実行時刻になるもの その時点ではターンを開始せず、その入力の後に実行されます 対応は不要です
CronCreate などのツールでスケジュールされた定期ジョブで、ユーザーの次の入力より前に実行時刻になるもの その時点ではターンを開始しません。ジョブはその回の実行を失いますが、スケジュールは維持されます。 対応は不要です
mod が開始したモニターからのイベント モニターは実行され続けます。ユーザーの次の入力より前にモニターが発生させたイベントはドロップされます。ただし、モニターの終了通知は例外で、その入力の後に届きます。 対処する方法はありません
mod が生成し、後で $.session.send で再開したサブエージェントで、ユーザーの次の入力より前に終了するもの 終了通知は待機し、その入力の後に会話に届きます 対応は不要です
ユーザーがターンを停止した時点ですでに実行中で、ユーザーの次の入力より前にタイムアウトに達する、$.tool.call からのフォアグラウンドの Bash または PowerShell コマンド バックグラウンドに移動する代わりに、タイムアウトで停止されます。呼び出しは isError が設定された状態で解決し、出力には Ended instead of moving to the background: plugins' input is held, by a Stop or by the host's limits. という行が含まれます。 ユーザーの次の入力の後に、拒否またはドロップされた呼び出しを再試行するの方法で再実行します

ユーザーの次の入力より前にスケジュールされたジョブが実行時刻になるか、モニターがイベントを発生させた場合、ユーザーには Stopped: トーストが表示されることがあります。

claude -p 実行と Agent SDK セッションで拒否される呼び出し

claude -p 実行または Agent SDK セッションでは、{ drop: 'the run reached its --max-turns limit' } のような結果は、実行が mod の呼び出しを拒否したことを意味します。

拒否された $.prompt.submit は { drop: '<reason>' } で解決します。$.command.run や $.agent.spawn など、その他の拒否された呼び出しは、拒否された呼び出しが返すものに示す結果を返し、<reason> の部分に理由が入ります。以下で該当する <reason> を見つけ、対処方法を確認してください。

  • the run reached its --max-turns limit: mod のプロンプトによるターンと、mod が生成したサブエージェントのターンは、--max-turns または Agent SDK のターン制限にカウントされます。より多くのターンが必要な場合は、より高い制限で実行を開始してください。それができない場合は、拒否された呼び出しを行わないよう mod を変更してください。実行がユーザーまたは SDK アプリケーションから別のプロンプトを受け取ると、ターン数はリセットされ、実行は再び呼び出しを受け付けます。
  • the run is past its --max-budget-usd limit: より高い --max-budget-usd で実行を開始するか、Agent SDK の支出制限を引き上げてください。それができない場合は、拒否された呼び出しを行わないよう mod を変更してください。この制限は、実行のこれまでの合計コストに適用されます。実行が別のプロンプトを受け取っても合計は加算され続けるため、再試行しても同じ理由が返ります。
  • the host has ended its input: たとえば、SDK アプリケーションがストリーミング入力を終了した場合や、--input-format stream-json で開始した実行で stdin が閉じた場合です。実行は終了しつつあり、それ以上の入力を受け付けないため、再試行しても同じ理由が返ります。拒否された呼び出しを行わないよう mod を変更してください。自分で実行を開始する場合は、次回から mod が行う呼び出しがなくなるまでその入力を開いたままにしてください。
  • the session was stopped and nobody has sent input since: 実行が中断されました(たとえば Agent SDK の interrupt() によって)。ユーザーまたは SDK アプリケーションからの次のメッセージの後に再度呼び出しを行うには、拒否またはドロップされた呼び出しを再試行するの手順に従ってください。

mod を使用した claude -p 実行が終了しない

mod がターンの終了ごとにプロンプトを送信すると、各プロンプトが別のターンを開始します。--max-turns または --max-budget-usd がない場合、その mod を読み込む claude -p 実行は、自分で停止するまで続きます。

実行が自動的に終了するようにするには、シェルでコマンドにいずれかのフラグを追加します。他のユーザーが実行を開始する場合は、作業が完了したらプロンプトの送信を停止するよう mod を変更してください。次のコマンドは --max-turns を追加します。

claude -p "find the TODOs" --max-turns 5

実行がその制限に達すると、mod の次のプロンプトはターンを開始せずに拒否され、実行は終了します。

それでも実行が制限を超える場合は、シェルで claude --version を実行し、バージョンが v2.1.304 より前であれば Claude Code を更新してください。mod のプロンプトは v2.1.299 以降、制限に達すると拒否されます。mod がサブエージェントを生成する場合、それらのターンは v2.1.304 以降 --max-turns にカウントされます。

セッション間でメッセージを送受信する

mod は、自分の別のセッション、このセッションのサブエージェントの 1 つ、またはそのエージェントチームのチームメイトにプレーンテキストメッセージを送信できます。また、到着して離れるメッセージを観察することもできます。

メッセージを送信するには、$.session.send({ to, text }) を呼び出します。これは SendMessage ツールが行う配信と同じです。to は受信者に応じて設定します。

  • 自分の別のセッション: { sessionId }
  • サブエージェントまたはチームメイト: { agentId }($.agent.list() から取得した ID を使用)
  • 受信したメッセージの送信者: そのメッセージの送信元の文字列アドレス

呼び出しはメッセージがキューに入ったら解決し、{ isDelivered: true } で解決します。何も配信されなかった場合、{ isDelivered: false, reason } で解決し、reason は理由を述べます。

このフックは、コマンドとして登録された /ping コマンドに答え、その後に入力したセッション ID のセッションにステータスを尋ねます。

on('command.run', { command: 'ping' }, async ($, e) => {
  // e.args は /ping の後に入力されたセッション ID
  const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
  // 呼び出しはどちらの場合でも解決するため、isDelivered をチェックして何が起こったかを学習
  if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
  // 空の結果はこのセッションのトランスクリプトに何も出力しない
  return {}
})

メッセージがキューに入ると、セッションに何も表示されず、他のセッションの Claude は Status? One line. を読みます。何も配信されなかった場合は、トースト通知で理由が表示されます。

session.receive と session.send により、mod はメッセージを観察できます。両方から next(e) を返して、各メッセージを変更されずに渡します。

イベント 発火するタイミング 有用なフィールド
session.receive メッセージがこのセッションに到着し、Claude がそれを読む前 e.text、および e.origin.kind(別のセッションまたはエージェント、task-notification、または scheduled-trigger の場合は peer または peer-send-message など)。{ consumed: reason } を返して Claude から保持します。
session.send メッセージが SendMessage ツールまたは mod から離れようとしている e.to、e.text、および e.origin.kind(model または plugin)

インバウンドメッセージを拒否 するように設定されたセッションは、session.receive が発火する前にメッセージを拒否するため、フックはそれを見ません。承認待ちのメッセージはフックに最初に到達するため、mod はまだ承認していないメッセージを読むことができます。フックの next(e) はメッセージが配信されないときに拒否します。

受信したメッセージの送信者の名前は、送信者が書いたものなので、それに基づいて決定しないでください。

ファイル、プロセス、ネットワークにアクセスする

mod は、Claude Code を実行しているユーザーと同じ権限で、mods API を通じてファイルシステム、プロセス、ネットワークにアクセスします。フックモジュール自体には Node.js API、setTimeout などのタイマーグローバル、独自のネットワークまたはファイルアクセスはありません。URL、TextEncoder、AbortController、crypto.subtle などの標準 JavaScript および Web API が利用可能です。以下の各名前空間は、1 種類のアクセスをカバーしています。

名前空間 何をするか
$.fs read(path)、write(path, text)、exists(path)、stat(path)、list(path) はファイルとディレクトリで動作
$.process run(['git', 'status']) はコマンドを開始し、終了時に解決。spawn は長時間実行コマンドの出力をストリーム。
$.http http または https 上の fetch(url, init)。本文が読み込まれたら { status, ok, headers, text } に解決。
$.store プラグイン独自の JSON キー値ストア、セッション間で保持
$.env 環境変数を get および set。名前をリテラル文字列として記述。
$.settings read() はすべての設定を 1 つのオブジェクトにマージしたものに解決。{ source } または { key } を指定すると、代わりに1 つのソースの設定または 1 つのキーの値に解決。
$.session messages() はトランスクリプトを { role, text, toolUses } のリストとして返す。また、作業ディレクトリ、モデルなど。usage() はコンテキストウィンドウの使用とプラン制限を返す。
$.mcp 接続された MCP サーバーのツールを call

ファイル、プロセス、リクエストには、独自のいくつかのルールがあります。

  • パス:相対パスは、セッションの作業ディレクトリ、またはフックが処理しているイベントのサブエージェントの作業ディレクトリを基準に解決される
  • $.fs.list:1 つのディレクトリのエントリを { name, kind, size, isLink } として返し、再帰的には動作しない
  • $.process.run:引数リストを取り、シェルを使用しない。終了コードに関係なく { exitCode, stdout, stderr } に解決。プログラムが開始できないか、タイムアウト時にまだ実行中の場合は拒否します。デフォルトは 30 秒なので、try と catch でラップします。
  • $.http.fetch:最大 5 回までリダイレクトに従う。異なるオリジンへのリダイレクトでは、設定したリクエストヘッダーのうち accept、accept-language、content-type、user-agent のみを保持し、それ以外は破棄する。そのため、Authorization など別のヘッダーに依存するリクエストは、そのリダイレクト後に失敗する可能性がある。タイムアウトと本文のサイズについては制限を参照。

これらの呼び出しのそれぞれは、それ自体がイベントであり、$.fs.read の場合は fs.read など、$. なしで名前空間とメソッドに対して名前が付けられています。チェーンの前にある mod は、呼び出しを観察、書き直し、または拒否できます。これは、組織が mod が到達するものを制限する方法です。

mod は、コマンドが出力を生成した後や終了した後でも $.process.spawn の呼び出しを拒否でき、その場合もコマンドが行ったことは何も元に戻されません。このとき呼び出しは拒否され、そのメッセージの末尾には次のいずれかの文字列と、拒否した mod の理由が付きます。

  • $.process.spawn started, and a plugin withheld its result::拒否した mod がコマンドの出力を最後まで読み取っていなかった。コマンドがまだ実行中の場合、Claude Code はコマンドを停止する。
  • $.process.spawn ran, and a plugin withheld its result::拒否した mod がコマンドの出力を最後まで読み取っていたため、コマンドはすでに終了していた

すべての設定、1 つのソース、または 1 つのキーを読み取る

$.settings.read() は、使用中のすべてのソースの設定を優先順位に従ってマージした 1 つのオブジェクトに解決されます。代わりに 1 つのソースを読み取るには { source } を、1 つのキーを読み取るには { key } を渡します。両方を渡すこともできます。

呼び出し 解決される値
$.settings.read() マージされた設定を 1 つのオブジェクトとして返す。そのトップレベルのキーは、permissions や hooks など、settings.json ファイルのキーと同じ。
$.settings.read({ source: 'policy' }) その 1 つのソースの設定を 1 つのオブジェクトとして返す。そのソースに設定がない場合は {}
$.settings.read({ key: 'spinnerTipsEnabled' }) マージされた設定におけるそのキーの値。キーが設定されていない場合は undefined
$.settings.read({ key: 'spinnerTipsEnabled', source: 'user' }) その 1 つのソースにおけるそのキーの値。そのソースで設定されていない場合は undefined

source には、読み取る 1 つの設定ソースの名前を渡します。

  • user:~/.claude/settings.json
  • project:.claude/settings.json
  • local:.claude/settings.local.json
  • flag:--settings で渡したもの、および SDK のインライン設定
  • policy:管理設定

key には、1 つのトップレベルキーの正確な名前を渡します。ネストされたオブジェクトへのドット区切りのパスは指定できません。key 引数には Claude Code v2.1.304 以降が必要です。

次のステップ