hughescr/claude-code-config/tree/develop/my-plugins/mods-probe
mods-probe
セッションごとに hooks モジュールが有効かどうかを記録し、推定した無効状態を突き合わせる Claude Code hooks-module フラグプローブ。
この mod について
mods-probe
これは Claude Code の hooks モジュール(「mods」)のフラグを追跡するプローブだ。mods は、サーバー側の GrowthBook フラグ
tengu_plugin_hooks_modules がプロセスで ON の間だけ読み込まれる。OFF の間は、すべての hooks モジュールが黙ってスキップされる。
このプラグインは、その沈黙を証拠に変える。
session.start でモジュールは2つの処理を行う。
- トランスクリプトに1行書く。
mods-probe: hooks modules are ON in this process (logged to ~/.claude/mods-flag.log)という行だ。トーストではなくトランスクリプト行($.ui.log)を選んだのは、トーストが4 sしか表示されず、起動時には見逃したりまだ描画されなかったりするからだ。 行は残り、-p、SDK、デスクトップのホストにはui_logとして届く。 ~/.claude/mods-flag.logに1行追加する。
2026-10-01T12:34:56.789Z session=<id|?> surface=<terminal|desktop|vscode|mobile|none> interactive=<true|false> version=<claude version|?> on
起動時点でまだどこにも描画しないホスト(SDK やデスクトップの一部のセッション)では、surface=none は正常だ。
ログを読む
フラグが OFF の間はモジュールが動かないため、プローブ単体で言えるのは「ON」だけだ。記録がない場合は OFF か不明を意味する。
OFF 側は、付属の SessionStart command hook が担う。~/.claude リポジトリにある hooks/mods-flag-reconcile.ts を
~/.claude/settings.json に接続し、/opt/homebrew/bin/bun で実行する。設定側のフックはフラグで制御されないため、すべてのプロセスで動き、同じログに追記する。
2026-10-01T12:34:56.000Z session=<id> source=<startup|resume|fork> pid=<claude pid|?> start
2026-10-01T12:34:56.789Z session=<id> surface=terminal interactive=true version=2.1.287 on # this plugin
2026-10-01T12:38:10.000Z session=<id> pid=<claude pid|?> for_start=2026-10-01T12:34:56.000Z inferred off
3種類の記録は同じ文法だ。<ISO time> key=value... <state> で、最後の token が状態(start、on、off)になる。session= は必須だ。ルールは次のとおり。
- 新しい claude プロセスはそれぞれ1つの
startを書く。/clearと compact はプロセスを維持するので、プローブのsession.startは発火せず、何も書かない。resume がstartを書くのは新しいプロセスの場合だけだ(起動してからstartのない pid)。 startが2分より古く、同じセッションのonがその前30 sから後5分までにない場合、次にどのプロセスがセッションを開始してもinferred offを1行書く。この行自体が再推定を止める印になる。プローブが session id を読めなかったセッション(session=?)のonは、その近くにあるすべてのstartに対して有効だ。- つまり
on= mods が ON、inferred off= プロセスは起動したがonを一度も記録しなかった。2分未満のstartはまだ保留中だ。
レポート(読み取り専用。まだ off の印が付いていないが対象になっている start も数える。日付は UTC で、off は start の日付に数える)。
bash
bun ~/.claude/hooks/mods-flag-reconcile.ts --report
# day starts on off
# 2026-10-01 6 4 2
# total 6 4 2
# latest: off at 2026-10-01T12:34:56.000Z
# last transition: on -> off at 2026-10-01T12:34:56.000Z
生の表示(数えるのは on/off の行だけで、start 行が分母だ)。
bash
tail -n 20 ~/.claude/mods-flag.log
grep ' on$' ~/.claude/mods-flag.log | cut -dT -f1 | sort | uniq -c # on-sessions per day
grep ' off$' ~/.claude/mods-flag.log | cut -dT -f1 | sort | uniq -c # inferred offs per day (by inference time)
制限
セッション実行中にモジュールを再読み込み(ホットリロード、enable)すると session.start がもう一度発火するため、1つのセッションで on が2回記録されることがある。プローブは各 on のたびに read-modify-write でファイルを最新の 2000 行に切り詰めるので、その瞬間にフックが追加した start が失われることがある(問題はない。失われた start は off を生まない)。2つのプローブが同時に書くと on が失われ、誤った off に見えることがある。
フックは、サイズ超過(1 MiB超)を原子的な rename で切り詰める場合を除きファイルを書き換えない。この処理は mods が十分長く OFF でプローブが切り詰めていない場合だけ行う。プローブが再開したセッションの id をフックと同じように報告できないと、再開したプロセスが off に見えることがある。見つからない claude pid(サンドボックス化された ps)は pid=? と記録される。
次のコマンドでキャッシュ済みフラグ値を照合できる。
jq '.cachedGrowthBookFeatures.tengu_plugin_hooks_modules' ~/.claude.json
読み込み方法
craigs-claude-plugins マーケットプレイスからインストールし、~/.claude/settings.json の enabledPlugins で有効化する。この設定は、このマシン上のどのディレクトリにあるセッションにも適用される。CLI(claude-smart.sh の有無を問わない)と、ユーザー設定を共有するデスクトップアプリが対象だ。claude-smart.sh --plugin-dir は使わない。プロジェクト種別ごとにしかプラグインを読み込まず、デスクトップアプリには届かない。
モジュールを編集したら .claude-plugin/plugin.json の version を上げ、次を実行して
claude plugin marketplace update craigs-claude-plugins && claude plugin update mods-probe@craigs-claude-plugins
セッションを再起動する。my-plugins/MODS-ACTIVATION.md も参照。
確認
bash
claude plugin validate my-plugins/mods-probe
DISABLE_GROWTHBOOK=1 claude plugin test my-plugins/mods-probe # one-off only: never put this in a launcher or settings
インストール
まず作者の README で marketplace とプラグイン名を確認してください。コマンドはリポジトリの構成によって変わる場合があります。
claude plugin marketplace add hughescr/claude-code-config claude plugin install mods-probe
原文 / README
mods-probe
A flag tracker for Claude Code hooks modules ("mods"). Mods load only while the server-side GrowthBook flag
tengu_plugin_hooks_modules is ON for the process; while it is off, every hooks module is silently skipped. This
plugin turns that silence into evidence.
On session.start its module does two things:
-
Writes one transcript line:
mods-probe: hooks modules are ON in this process (logged to ~/.claude/mods-flag.log). A transcript line ($.ui.log) was chosen over a toast: a toast lasts 4 s and can be missed or not yet drawn at launch, while the line persists, and-p/SDK/desktop hosts receive it asui_log. -
Appends one line to
~/.claude/mods-flag.log:2026-10-01T12:34:56.789Z session=<id|?> surface=<terminal|desktop|vscode|mobile|none> interactive=<true|false> version=<claude version|?> onsurface=noneis normal for a host that draws nowhere yet at start (some SDK/desktop sessions).
Reading the log
The module never runs while the flag is off, so the probe alone can only say "on": silence from it means off or
unknown. The "off" half comes from a companion SessionStart command hook, hooks/mods-flag-reconcile.ts in
the ~/.claude repo (wired in ~/.claude/settings.json, run with /opt/homebrew/bin/bun). Settings hooks are not
gated by the flag, so it runs in every process and appends to the same log:
2026-10-01T12:34:56.000Z session=<id> source=<startup|resume|fork> pid=<claude pid|?> start
2026-10-01T12:34:56.789Z session=<id> surface=terminal interactive=true version=2.1.287 on # this plugin
2026-10-01T12:38:10.000Z session=<id> pid=<claude pid|?> for_start=2026-10-01T12:34:56.000Z inferred off
One grammar for all three: <ISO time> key=value... <state>, the last token is the state (start, on,
off), session= is required. The rules:
- Each new claude process writes one
start./clearand compact keep the process and the probe'ssession.startdoes not fire for them, so they write nothing. A resume writes astartonly for a new process (a pid with nostartsince it launched). - A
startolder than 2 minutes with noonfor the same session from 30 s before to 5 minutes after it gets oneinferred offline on the next session start in any process. That line is also the marker that stops it being inferred again. Theonof a session whose id the probe could not read (session=?) counts for any start near it. - So
on= mods were on;inferred off= the process started and never loggedon. Astartyounger than 2 minutes is still pending.
Report (read-only, also counts starts that are due an off but not yet marked; days are UTC, an off counts on its
start's day):
bun ~/.claude/hooks/mods-flag-reconcile.ts --report
# day starts on off
# 2026-10-01 6 4 2
# total 6 4 2
# latest: off at 2026-10-01T12:34:56.000Z
# last transition: on -> off at 2026-10-01T12:34:56.000Z
Raw views (only on/off lines count; start lines are the denominator):
tail -n 20 ~/.claude/mods-flag.log
grep ' on$' ~/.claude/mods-flag.log | cut -dT -f1 | sort | uniq -c # on-sessions per day
grep ' off$' ~/.claude/mods-flag.log | cut -dT -f1 | sort | uniq -c # inferred offs per day (by inference time)
Limits. A reload of the module while a session runs (hot reload, enable) raises session.start again, so a
session can occasionally log on twice. The probe trims the file to its newest 2000 lines on each on, by
read-modify-write, so a start the hook appended during that instant can be lost (harmless: a lost start never
produces an off). Two probes writing in the same instant can lose an on, which would show as a false off.
The hook never rewrites the file except to trim an oversized one (over 1 MiB, only when mods have been off long
enough for the probe not to trim), by an atomic rename. If the probe does not report the resumed session's id the
way the hook sees it, a resumed process can look like an off. A claude pid that cannot be found (a sandboxed
ps) is logged as pid=?.
Cross-check the cached flag value with jq '.cachedGrowthBookFeatures.tengu_plugin_hooks_modules' ~/.claude.json.
How it loads
Installed from the craigs-claude-plugins marketplace and enabled in ~/.claude/settings.json
(enabledPlugins), which covers every session on this machine in any directory: the CLI (with or without
claude-smart.sh) and the desktop app, which share user settings. claude-smart.sh --plugin-dir is not used: it
only loads plugins by project type and never reaches the desktop app.
After editing the module, bump version in .claude-plugin/plugin.json, then
claude plugin marketplace update craigs-claude-plugins && claude plugin update mods-probe@craigs-claude-plugins
and restart sessions. See my-plugins/MODS-ACTIVATION.md.
Check it
claude plugin validate my-plugins/mods-probe
DISABLE_GROWTHBOOK=1 claude plugin test my-plugins/mods-probe # one-off only: never put this in a launcher or settings