hughescr/claude-code-config/tree/develop/my-plugins/mods-probe
mods-probe
세션마다 hooks 모듈이 켜져 있는지 기록하고 추론된 꺼짐 상태를 대조하는 Claude Code hooks-module 플래그 탐침입니다.
이 mod 소개
mods-probe
Claude Code hooks 모듈(“mods”)플래그를 추적하는 탐침입니다. 서버 측 GrowthBook 플래그
tengu_plugin_hooks_modules가 프로세스에서 ON일 때만 mods가 로드되고, 꺼져 있으면 모든 hooks 모듈이 조용히 건너뛰어집니다.
이 플러그인은 그 침묵을 증거로 바꿉니다.
session.start에서 모듈은 두 가지 일을 합니다.
- 트랜스크립트 한 줄을 씁니다:
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에 한 줄을 추가합니다.
2026-10-01T12:34:56.789Z session=<id|?> surface=<terminal|desktop|vscode|mobile|none> interactive=<true|false> version=<claude version|?> on
시작할 때 아직 어디에도 그리지 않는 호스트(일부 SDK/데스크톱 세션)에서는 surface=none이 정상입니다.
로그 읽기
플래그가 꺼져 있으면 모듈이 실행되지 않으므로 탐침만으로 말할 수 있는 것은 “켜짐”뿐입니다. 기록이 없으면 꺼짐 또는 알 수 없음입니다.
“꺼짐” 쪽은 함께 쓰는 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
세 가지 기록은 같은 문법을 씁니다: <ISO time> key=value... <state>. 마지막 token이 상태(start、on、off)이고 session=은 필수입니다. 규칙은 다음과 같습니다.
- 새 claude 프로세스마다
start하나를 씁니다./clear와 compact는 프로세스를 유지하므로 탐침의session.start가 발생하지 않아 아무것도 쓰지 않습니다. resume이start를 쓰는 것은 새 프로세스일 때뿐입니다(시작 이후start가 없는 pid)。 start가 2분보다 오래되었고 그 전 30 s부터 그 후 5분까지 같은 세션의on이 없으면, 다음 세션 시작 때 어느 프로세스에서든inferred off한 줄을 추가합니다. 이 줄이 다시 추론되는 것을 막는 표식이기도 합니다. 탐침이 세션 id를 읽지 못한 세션(session=?)의on은 그 근처의 모든start에 적용됩니다.- 따라서
on= mods가 켜짐;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)
제한
세션 실행 중 모듈을 다시 로드하면(핫 리로드, 활성화)session.start가 다시 발생해 한 세션에서 on이 두 번 기록될 수 있습니다. 탐침은 on마다 read-modify-write로 파일을 최신 2000줄로 줄이므로 그 순간 훅이 추가한 start가 사라질 수 있습니다(문제 없음: 사라진 start는 off를 만들지 않습니다). 두 탐침이 동시에 쓰면 on 하나가 사라져 잘못된 off로 보일 수 있습니다.
훅은 파일이 너무 클 때(1 MiB 초과이고 mods가 탐침의 정리 없이 충분히 오래 꺼져 있을 때만)원자적 이름 변경으로 줄이는 경우를 제외하고 파일을 다시 쓰지 않습니다. 탐침이 재개된 세션 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