plugin-runtime-debug について
plugin-runtime-debugは、DSH Webプラグインの実行時問題を調べる開発者向けスキルです。ホストソースのAPI契約とブラウザーの挙動を確認し、症状に基づいて原因を追跡してからプラグインを変更するよう導きます。
主な機能
- 01変更前にホストAPI契約を確認
- 02オフセットと表現の不一致を追跡
- 03ブラウザー固有のURL挙動を診断
- 04表示症状から実行時原因を特定
- 05操作を繰り返して修正を検証
活用シーン
- 01無応答に見えるプラグイン呼び出しの調査
- 02古い、または実在しないUI表示の診断
- 03ブラウザー限定のリソース検索障害の追跡
- 04クライアントバンドルやプラグイン起動の障害調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ oh-my-dsh/ dsh-plugin-upgrade-skill --skill plugin-runtime-debugインストール後、Claude Code で/plugin-runtime-debugを入力して読み込まれたか確認します。
同梱ファイルREADME.mdreferences/browser-forensics.md
詳細
DSH Web プラグインのランタイム動作をデバッグする
外部 Web プラグインは、契約がプラグイン独自の型ではなく DSH の ソースツリーに記載されている、ホストクライアント API を呼び出します。実行時に動作が 意図と異なる場合、ほとんどの場合、契約の読み違いが原因です。原因の診断には、API の 名前ではなく、必ずホストのソースを確認してください。
基本ルール:まずホストのソースで動詞の契約を読む
ホスト API の呼び出しを変更する前に、DSH のソースチェックアウト
(~/.dsh/source/current またはベンダー提供のコピー)で実装パッケージを開き、
実際のメソッド(ドキュメントコメント、ガード、比較対象の型)を確認してください。
プラグインが渡すすべての値について、同じ確認を繰り返します。ほとんどの事例は、
次の4つの質問で把握できます。
- オフセットはどのテキストを基準に数えますか? 動詞がスパンや
オフセットを受け取る場合、数値がどの文字列をインデックスするのかを確認します。
公開スナップショットのフィールドと内部エディターの射影は、必ずしも同じ文字列とは限りません。
ある表現のオフセットを、別の表現と比較するガードを持つ動詞に渡すプラグインは、
呼び出しが失敗しても何も通知されません。呼び出しは
falseを返すか、何もせずに終了します。 - 各表現では、1つの「単位」はどれだけの幅を占めますか? ドキュメントに不透明な インライン単位(チップ、トークン、添付ファイル)が含まれる場合、その単位が公開フィールドと 動詞のガードが参照する射影の両方で、同じ幅を占めるかを確認します。幅が異なる場合、 単位が存在しない間しかオフセットは正しくありません。最初の呼び出しが成功し、 それ以降の呼び出しがすべて失敗することから何がわかるかを確認してください。
- 動詞が処理を拒否したとき、誰がそれに気づきますか? 成功・失敗を返す動詞が 何も通知せずに失敗すると、後続の状態バグにつながります。呼び出し元は自分の管理情報を 削除してしまう一方で、UI はオブジェクトが消えていないにもかかわらず、その隣に 「見つからない/利用できない」プレースホルダーを表示します。結果を無視して呼び出し、 そのまま状態をクリーンアップする、というパターンがないか、すべての呼び出し箇所を監査してください。
- この行を評価するエンジンはどれですか? Node で実行するテストでは検証できない
Web プラットフォームの動作は、古いエンジンが現在の標準より前に作られているため、
ユーザーのブラウザーでは異なる場合があります。既知の例は、非特殊スキームの URL 解析です。
WHATWG URL Standard では、
new URL('dsh-resource://file/…').hostnameは"file"である 必要があり、Node と現行の Chromium はその値を返します。しかし、非特殊 URL の標準準拠の 解析方式に変更される前の Chromium では""が返されます。そのため、古い Chromium ベースの Edge ビルドでは何も通知されずに""が返ります。この現象を確認したら、正確なブラウザーの バージョンを記録してください。カスタムスキーム上でURL.hostname/.pathnameを使って ルーティングするホストコードやプラグインコードは、Node ベースのテストではすべて動作し、 影響を受ける実ブラウザーでのみ失敗します。カスタムスキームの URL プロパティを信用する前に、 実際のブラウザーエンジンで検証するか (references/browser-forensics.mdを参照)、文字列を手動で解析してください。
症状の分類と原因の見つけ方
- 最初の操作は成功するが、それ以降は毎回エラーになる — 先行する呼び出しが書き込んだ状態によって、プラグインが計算する値と動詞が期待する値の対応関係が変わっています。挿入の前後で2つの表現を比較し、ホストのソースにある単位幅から補正値を導き出してください。そのうえで、エラーが発生した箇所だけでなく、オフセットを渡すすべての呼び出し箇所に同じ導出方法を適用してください。
- 削除ボタンを押すと、プレースホルダーのラベルが付いた行が残る — すでに記録情報が削除されたあとで、削除動詞が処理を拒否しています(質問3を参照)。動詞の戻り値で確認し、削除が実際に適用されてから記録情報を破棄してください。
- 派生 UI に古いエントリや実在しないエントリが表示される — 事実の正となる情報源を特定し、そこからビューを導出してください。再調整や再マウント中に一時的なスナップショット(空になる瞬間)が発生するとエントリを破棄するような、購読付きのプラグイン側キャッシュは、有効なエントリまで削除してしまいます。判断時には公開済みの最新状態を読み取り、キャッシュはあくまで高速化のためにのみ使用してください。
- 更新/バージョンのチップに誤った「最新」が表示される — リモートのタグおよび raw ファイルのエンドポイントは CDN にキャッシュされており、実際のプッシュから数分遅れることがあります。実行中のビルドより古い可能性があるリモート取得値を、確定情報として表示してはいけません。実行中のバージョンを基準に「最新か更新か」を判断し、2つの値のうち新しいほうを表示してください。
- 登録、合成、モジュールの有効化がすべて正常に確認できているのに、ユーザーのブラウザーで組み込み画面に汎用的な「サービスを利用できません」というフォールバックが表示される — 登録の調査をやめ、失敗しているコードがプラットフォームから何を読み取っているかを調べてください。実例(0.1.6-alpha.2、2026-09):右サイドバーのドキュメントプレビューで、すべてのファイルに「文件资源服务不可用。」と表示されていました。
fileリソースプロバイダーは登録済みで、__DSH_BOOT__も完成しており、有効化の失敗は0件でした。しかしプロバイダールーターがdsh-resource://アドレスのプロトコルをnew URL(address).hostnameで取得しようとしたところ、ユーザーの Edge では""が返され、検索が常に失敗してリクエストが一度も送信されていませんでした。プレースホルダーの文言が示すのは機能であって、障害の発生箇所ではありません。ルーターに計測を仕込んで、決定的なログ行(parsedHost="")を得る必要がありました。報告できる情報がフォールバック文字列だけの場合は、登録状態について結論を出す前に、操作可能なブラウザーで計測済みバンドルを使って再現してください(references/browser-forensics.md を参照)。この同一事象は、以前「プロバイダーが気付かれずに登録解除された」と誤診されていました。 - リリース後、スロット全体の UI がひっそりと消える — スロットコンポーネント内の式で例外が発生すると(典型例は、別のコンポーネントの状態変数をスコープ外で参照するなどの未解決の識別子です)、フレームワークのスロットレベルのエラーバウンダリーが例外を捕捉し、エントリ全体をアンマウントします。エラーはコンソールにしか表示されないため、ユーザーは「チップ/パネルが消えた」とだけ報告します。作者がすぐに気付けないのは、2つの遅延要因があるためです。
||の短絡評価によって、左オペランドが false になるまで式が評価されないことと、空の状態で早期リターンするコンポーネントでは実データが描画されるまで式が評価されないことです。最新の差分を最初から疑わないでください。ロールバックによる二分探索、またはデータを用意した最小限のレンダーマウントで切り分け、例外を発生させる行が以前のリリースに含まれていないか確認し、参照を削除して修正してください(そのような状態はローカルスコープ内で定義してください)。スロットコンポーネントを手軽に堅牢化するには、防御的な読み取り(x?.items ?? [])と、オプショナルチェーンを使った DOM アクセス(target.closest?.())を使用してください。エラーバウンダリー内で例外が起きると、スロット全体が犠牲になります。 - リポジトリの編集内容が GUI に反映されない/プロファイルの node_modules 内で EBUSY が発生する — まずインストール方式を確認してください。
Get-Item <profile>/node_modules/<pkg> | Select LinkType, Targetを実行するか、cordis.patch.yml 内のlink:<path>マーカーを確認します。Junction/リンクによるインストールは、リポジトリの作業ツリー自体がインストール先です。コピー作業は存在せず、必要もありません。node_modules への Copy-Item は、何も起きないのがせいぜいです。EBUSY の原因は実行中の dsh ホストプロセスです(ブラウザーを閉じても解放されません)。また、ホスト再起動後もブラウザーがクライアントバンドルをキャッシュして配信する場合があります。リンクインストールされたライブラリのみのプラグインを有効化するには、ホストを完全に停止し、dsh webを再起動してからハードリフレッシュを行い、読み込まれたバージョンマーカーを確認してください。解決されていないパス配下のファイルを、退避のために名前変更してはいけません。Junction を介すと、異なる2つのディレクトリは実体として同じであり、名前変更によって唯一のコピーが移動します。ソースからホストを起動する場合(ハーネスのチェックアウト内でpnpm dsh webを実行する場合)は、さらに2つの制約があります。クライアントバンドルの組み合わせは起動時に一度だけ作成され(HMR では再ビルドされないため、プラグインを編集するたびにホストを完全に再起動する必要があります)、Windows では終了中のプロセスツリーがリスナーのポートを解放しないため、プロセスツリー全体を停止する必要があります(taskkill /PID <pid> /T /F)。そうしないと、次回の起動は EADDRINUSE で失敗します。 - クライアントバンドルに生の ESM が含まれるプラグインが1つあるだけで、すべてのプラグインが停止し、エラーには無関係なエントリが表示される — ホストはすべてのクライアントバンドルを1つのクラシックな
<script>バンドルに連結します。トップレベルのimportが1つでもあると、数 MB のバンドル全体でコンパイルに失敗し、プラグインは一切登録されず、ブラウザーにはfailed to import loader entry <first-entry>と表示されます。ここで示されるのは、最初に await されたエントリ(多くの場合、まったく問題のないdsh-typert-registry)であって、原因ではありません。表示されたエントリを追いかけないでください。バンドルが再び読み込まれるようになるまで、プロファイルのinsert行を二分探索するか、疑わしいバンドルをnode --checkに通してください。クライアント側は素の ESM モジュールではありません。window.__ModuleLoader__.load({ id, factory })を通じて登録し、ファクトリー内でrequire("react")を使って react を取得し、inject/applyをエクスポートする必要があります。 - ターミナルのスプライトの右端に現れる謎のピクセル(輪郭、Z 記号、ハート)と、フレーム切り替え後に残るゴーストピクセル — どちらもフレームデータには現れない、半ブロック ANSI レンダリングの不具合です。(a)片側だけ塗りつぶされたセル(半分だけに色を付けた上半分ブロック)は、前景色を設定する一方で、PREVIOUS セルの SGR 背景色をそのまま残します。SGR はセル間で維持されるため、古い背景色が空白側に謎のピクセルを描画します。半分だけ塗りつぶされた各セルで明示的にリセットしてください(ESC[49m)。(b)末尾の透明セルを削除した行では、幅の狭いフレームを描画すると、削除位置より右側に前のフレームのピクセルが残ります。各行をスプライトの全幅にわたって描画し(透明セルは通常のスペースとして描画)、行末消去(ESC[K)で閉じてください。また、フレームデータ自体も固定してください。手作業で移植したスプライトのフレームは、元のアートから少しずつ数セルずれることがあり、抜粋部分だけを使った回帰テストでは見逃します。各フレームのダイジェストを元データと照合し、その一致をアサートしてください。
- ヘッド/UI プロセスが、処理完了後も数分間(または CI のタイムアウトまで)ハングする — 再スケジュールされるタイマーチェーン(マウント中、アニメーションプランナーが
setTimeoutを永遠に再設定する)が、コンポーネントをアンマウントしないホスト(プローブやテスト用ホスト。GitHub ジョブのデフォルトタイムアウトは 6 時間です)でイベントループを動かし続けます。インタラクティブな TUI は、いずれにせよ TTY/stdin ハンドルによって実行を続けるため、チェーンの参照を解除してください(timer.unref())。プローブは処理を完了して終了し、実際のセッションには影響しません。機能を有効にしたことで、以前は正常に終了していたジョブがタイムアウトするようになった場合は、これを疑ってください。
ワークフロー
- 1 回再現し、ユーザーに表示される正確な文字列(トーストのテキスト、 チップのラベル、コンソール出力)を記録してください。これらはバグ報告の契約です。
- 各文字列を出力したコードパスに対応付け、境界で呼び出されるホストの動詞を特定してください。
- その動詞のホスト側ソースを開き、常に確認すべき 4 つの質問に答えてください。
- 修正を書く前に、不一致を正確に述べてください(どの表現、どのガード、 どの呼び出し箇所か)。述べられない場合は、ソースを十分に読み込めていません。
- 不一致がコンソール出力に現れない場合(汎用的な代替文言、何も起きない処理、またはユーザーのブラウザーでのみ確認できる相違など)は、 一時的に計測を追加したバンドルを使って、操作可能なブラウザーで再現してください(バックアップを取り、決定的なログ行を 1 行追加してパッチを適用し、確認後に元に戻す)。 references/browser-forensics.md に従ってください。 ユーザーが貼り付けた DevTools のスクリーンショットは、症状の証拠であって、 原因の証拠ではありません。
- 報告された症状だけでなく、表現に依存する値を渡すすべての呼び出し箇所を修正してください。同じ不一致が、通常は異なる動詞を介して 2 つの機能を壊します。
- 失敗した操作手順で修正を検証してください。同じ操作を続けて 2 回行い、 どちらの試行でも同じ結果になることをアサートしてください。また、削除経路ですべてのビューからオブジェクトが消えることもアサートしてください。
- ライブラリのみのプラグインバンドル(ビルド手順なし)の場合は、手作業で埋め込んだバージョン定数を
package.jsonと同期させ、バンドルの構文を確認し(node --check)、ハードリフレッシュ後にブラウザーで検証してください。配信される成果物は、編集したファイルです。
コメント
Markdown 対応