A Claude Code plugin for running Claude Code well over time. One cohesive
capability across twelve skills and a family of telemetry-emitter hooks, including diagnosing why
most of an installed skill fleet never gets used.
audit-native-overlap maps native Claude Code surfaces against the current
repo's own components so a custom skill never silently duplicates what the
product now ships.
audit-install-state reports on the machine-scope ~/.claude install directory
itself. audit-performance captures slowness evidence at the moment a machine or
session feels slow so the cause is diagnosed instead of nuked, including the
fan-out layer of hooks, statusline, spawn cost, and subagent concurrency.
observability reads what your sessions actually did. known-issues tracks what
upstream has broken, and changelog integration keeps your repo current with what
upstream has shipped. The plugins skill keeps your own plugin fleet current,
morning-brief prints your read-only operator morning view, and lanes launches and
manages your loop lanes as background sessions. A
re-runnable setup action settles where the known-issues registry lives,
and the *-audit hooks feed observability with per-hook execution telemetry
Claude Code's native OTEL cannot see.
| Skill | What it does |
|---|---|
/claude-ops:audit-skill-visibility |
Audits whether the model can actually see each installed skill, the question behind "why does most of my fleet never get used?", since a skill the model cannot see can never be chosen. Reports three independent things per skill: reachability (visible, user-only by design, hidden by an override or disabled plugin, or invisibly misconfigured), observation (what usage was actually recorded, always horizon-qualified), and starvation (whether it is losing the description-budget contest: Claude Code drops descriptions starting with the skills you invoke least, so an unused skill loses the keywords a request would match and stays unused). Whether the listing overflows is computed from documented settings; which particular skills lose their descriptions is a labelled likelihood band, never an exact cutoff. Withholds every cold verdict the data cannot support instead of reporting absence of data as absence of use. Read-only. |
/claude-ops:audit-install-state |
Read-only audit of the machine-scope Claude Code installation directory, the ~/.claude tree plus the home-root ~/.claude.json. Inventories every file (entries labelled as an authored surface or a rolled-up bulk tree, with the complete per-file rows in a CSV artifact), separates what Claude Code's own cleanupPeriodDays sweep already manages from what nothing manages, resolves what each number in a filename actually is before attempting any process-liveness lookup, and deny-lists any subtree holding a revert ledger before classifying anything as stale. Never deletes; hands off to claude project purge and /disk-hygiene:clean. |
/claude-ops:audit-native-overlap |
Maps native Claude Code surfaces, built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills, against the current repo's plugin skills and agents. Bare invocation is a read-only overlap report (candidates with evidence, detection integrity floors carried through, and a shared-listing-budget exposure section); verdicts (prefer-native / prefer-ours / complementary / superseded / defer) are human-gated in a committed store (docs/native-surfaces/records.json) rendered into a generated registry (docs/NATIVE-SURFACES.md) whose every row carries an observable recheck trigger; only an explicit apply step bakes presence-gated native references into component descriptions and Boundary sections. |
/claude-ops:audit-performance |
Read-only slowness-diagnostic capture, run at the moment the machine or a session feels slow, before restarting or deleting anything. One timed engine pass separates four documented suspects: accumulated install-tree state, version regression, component bloat, and the fan-out layer, plus on Windows a kernel-object census that names the host-level Token-object leak beneath all four. Each suspect's evidence and verdict routing is documented in the skill. Phase timings are first-class evidence; content reads are allowlisted to four non-secret config files (settings.json, .last-cleanup, hooks.json, installed_plugins.json), so ~/.claude.json and history.jsonl stay stat-only. Reports and routes; never mutates, never elevates, and never executes a discovered hook or statusline command. |
/claude-ops:observability |
Reads locally captured Claude Code telemetry, OTEL DuckDB store, machine-owned collector, optional Aspire dashboard, hook-event JSONL, ccusage, and renders cross-session trend reports (session/day/week/month/since:/all scopes). Read-only except the explicit clean action, which prunes the JSONL log and OTEL store by age. |
/claude-ops:known-issues |
Searches known Claude product GitHub bugs before you build on a feature, checks service health and model quality, and maintains a persistent registry of tracked issues (what they block, workarounds, follow-ups when fixed). Actions: status (default), search, check-all, scan, list, quality, create. |
/claude-ops:changelog |
Ingests Claude Code changelog entries and integrates them into the current repo: fetch (read-only display), diff (impact triage over a release range, no edits), status (the read marker from the repo's Claude Code ledger, the default range to the newest release, and the replay cap), and apply (full explore → research → interview → implement pipeline, explicit user intent only). |
/claude-ops:plugins |
Brings a machine's plugin fleet current on demand: marketplace refresh, updates for the plugins that actually load (including in-repo project/local-scope installs), new-catalog-plugin install per policy, and scope-divergence detection. Actions: sync (default, CLI-mediated mutations only), audit (read-only dry run), converge (the one action that can touch a committed .claude/settings.json. Previews and confirms per plugin first). |
/claude-ops:morning-brief |
Prints the read-only, gh-based operator morning view for the current repo in one pass: open counts per queue label (priority: needs-triage, status: ready, status: needs-decision, needs-human), the gh-native merge-ready PR list (non-draft + mergeStateStatus=CLEAN), parked status: needs-decision issues with their RECOMMENDED lines, and loop-lane telemetry freshness (per-lane last-cycle age + flags:). Never mutates anything; the authoritative PR merge gate stays /source-control:babysit-prs. |
/claude-ops:lanes |
Starts, restarts, stops, and reports loop lanes as named background Claude Code sessions seeded from canonical prompt files. start (default) / restart pull the repo and refresh the plugin marketplace, then launch each configured lane (claude --bg -n <lane>) with its per-lane model/effort; status shows per-lane running state and live sessionId; stop ends a lane via claude stop; consume-restarts is the OS-schedulable restart-request consumer. It reads each configured lane's telemetry restart_request and relaunches the stopped lanes that asked, through the same launcher (#1653). Acts only on sessions whose name is a configured lane. Lanes come from a JSON config (--config, else $CLAUDE_OPS_LANES_CONFIG, else <repo>/.work/lanes/lanes.json, with a temporary default-only fallback to the pre-move <repo>/.work/lanes.json under a deprecation warning); config and prompts live in the reserved lanes/ concern home under a hardcoded .work root, which is a sanctioned placement but still session-local, so a durable cross-machine home stays #480's job. |
/claude-ops:setup |
check reports the effective known-issues-registry, skill-usage-log and hook-log-root destinations, their defaults, path containment, the hook log root's self-ignoring guard, and retired conventions (retirements.yaml), and prints the guidance for routing personal option changes through Claude Code's plugin configuration prompt; apply writes exactly one file, the guard inside the hook log root, and runs the gated retirement cleanup. |
Eight advisory *-audit hooks, registered as nine rows because
skill-usage-audit has two producers, emit the marketplace
hook-telemetry envelope. One
JSON event per run carrying that hook's own duration_ms, outcome, and a
privacy-safe subject. Each is independently toggleable via its own userConfig
boolean (default on; see Per-hook kill switches).
Three scripts serve those nine rows. hooks/audit-event-emitter.sh carries
seven of them and picks the row from the payload's hook_event_name, the way
session-event-log.sh next to it serves about thirty events from one file; the
seven events are distinct, so the event alone selects the row. Each row still
reads its own <name>_enabled switch and emits the same telemetry hook id,
hook_event, status and data fields it emitted as a standalone script, so
no downstream reader can tell the difference. The two rows with earned behavior
of their own keep their files: skill-usage-audit.sh, whose kill switch is
inlined above its library source because it sits on the hot PostToolUse
path, and hook-failure-audit.sh (below).
The hook budget is accounted per
hooks.json entry, and the collapse changes no entry: the same nine audit rows,
on the same events, with the same matchers, and one event still spawns exactly
one process. Per-entry cost is unchanged for an enabled row (the same library,
the same jq passes, plus one bash pattern match to read the event) and lower
when every switch is off, because the emitter reads all seven switches before it
parses the library. A row that is off on its own pays one buffered payload it
did not pay before, because the row is only known once the event has been read;
it parsed the same library then as now, and no extra process runs either way.
The six pure emitter rows are a no-op until a consumer wires a sink (below);
skill-usage-audit is one exception. Both its producers also write the shared
skill-usage.jsonl second store unconditionally (disable the whole feature with
skill_usage_audit_enabled=false; pick the store's home with skill_usage_scope
and skill_usage_dir). In the default repo scope the store dir is kept out of
git status via an idempotent machine-local .git/info/exclude entry
(skill_usage_git_exclude=false opts out for teams that commit the telemetry).
hook-failure-audit is the other exception. Its user-facing systemMessage
warning fires regardless of sink wiring (only its envelope needs a sink),
because its whole subject is failures nothing else surfaces: a hook that fails
to launch is a non-blocking error, the guarded tool call proceeds as if
approved, and the only durable trace is a transcript attachment no human reads
(#2577). It runs once per Stop, tails a bounded window of the session
transcript for hook_non_blocking_error attachments (structural match on the
attachment type, never substring), and warns once per session per distinct
failing hook registration, keyed on hookName plus the registered command because several plugins
share an event and matcher, re-warning when a new registration starts failing. It
lives in this
plugin, not in the plugin it might report on, deliberately: an in-plugin
detector shares its plugin's registration form and dies with it, which is
exactly how disk-hygiene's guard monitor missed the #1416 incident class.
Its budget share is stated as a process count, not a duration, and the host
is the reason. The hook-budget
convention gives the whole
always-on per-turn set 500 ms of parallel wall, and on the host in #3508 one
process creation costs 180-2,841 ms (median 1,108 ms at 501 concurrent
processes), so the count is what decides whether the set fits and a duration
measured anywhere else does not transfer. On a turn with no hook failure
recorded, the common case, the hook costs 9 process creations and 4 program
execs: one wc for the tail-cap decision, one grep pre-filter that opens
the transcript directly, and two jq passes inside the synced hook-utils.sh.
The creations above that are subshell forks inside the same shared library. It
was 18 creations and 6 execs before #3512. Counts are measured with strace -ff -e trace=clone,clone3,fork,vfork,execve, not bash -x: a command substitution
whose command carries its own redirection forks a subshell that xtrace cannot
see, and those forks were most of the cost. hook-failure-audit.test.sh
asserts both ceilings. Windows Git Bash, the host the convention binds to,
stays unmeasured for this row, so the parallel-wall figure there and the
comparison against 500 ms it feeds are still owed.
skill-usage-audit is captured by two disjoint producers so both invocation
paths are measured: the model-invoked Skill tool (PostToolUse) and the
user-typed slash command (UserPromptExpansion, which bypasses the Skill
tool). Events carry a source field (tool vs expansion) so consumers can
tell the paths apart; both share the same telemetry hook id and second store.
| Hook | Event | Emits | Why it earns its spawn |
|---|---|---|---|
api-error-audit |
StopFailure | API turn-failure error_type (never the message body) |
The event only fires when a turn fails at the API, so the hook costs nothing on a healthy session |
config-change-audit |
ConfigChange | the mutated config_source |
Matcher-scoped to the four sources that can change behavior, and a settings or skills change is rare |
instructions-loaded-audit |
InstructionsLoaded | <repo-relative-file>:<load_reason> (absolute prefix stripped; session_start filtered by default) |
The highest-volume row here, and the only one whose cost is worth watching. It is advisory: InstructionsLoaded ignores the exit code, so the hook never gates a load, and the session_start write-time filter keeps the noisiest reason out of the store by default |
permission-denied-audit |
PermissionDenied | classifier denials, Bash:<first-token> subject |
Fires only on an actual denial, and it is the only durable record of what the classifier refused |
pre-compact-audit |
PreCompact | compaction trigger (manual/auto) |
Once per compaction, which is the rarest event this plugin observes |
skill-usage-audit (tool path) |
PostToolUse (Skill) |
model-invoked skill; source: "tool"; also writes the skill-usage.jsonl second store |
Matcher-scoped to Skill, so it is skipped on every other tool call rather than firing per PostToolUse |
skill-usage-audit (expansion path) |
UserPromptExpansion | user-typed /command (slash_command/mcp_prompt); source: "expansion" + expansion_type; same second store |
Fires per expanded slash command, not per prompt, and it is the only path that sees a user-typed invocation (the Skill tool never runs) |
tool-failure-audit |
PostToolUseFailure | Write/Edit/Bash failures, privacy-safe subject | Matcher-scoped to the three tools whose failures are actionable, and the event fires only on failure |
hook-failure-audit |
Stop | unsurfaced hook_non_blocking_error attachments; envelope subjects are hook names only; also warns via systemMessage |
The only per-turn row, and it is the sole surface for a hook that failed to launch while its guarded tool call proceeded |
None captures a command body, absolute path, error message, or argument body, only category labels, privacy-safe subjects, and (for instructions-loaded-audit)
the repo-relative path of the loaded rule file.
The InstructionsLoaded row carries no matcher on purpose. That event's matcher selects on load
reason, and the row passes every reason through verbatim into its subject, so
scoping to the full documented set would skip nothing and would silently drop any reason a later
release adds. Scoping below that set is worse: the only reason worth excluding for cost is
session_start, which the row already drops at write time, and it drops it behind the
instructions_loaded_audit_log_session_start option. A matcher that excluded session_start would
stop the hook from ever spawning on it, leaving that option switched on but unable to log anything.
The row therefore stays unscoped until the option is retired.
Each audit hook is toggled by its own userConfig boolean (default on; set
to false for a clean no-op). Disable one hook without touching the others.
The hooks read them through the native CLAUDE_PLUGIN_OPTION_<KEY> hook-process
mirror. Seven rows share audit-event-emitter.sh, which reads the switch of the
row the event selected: sharing a script does not share a switch, and turning one
row off leaves the other six emitting.
| Hook | Option |
|---|---|
api-error-audit |
api_error_audit_enabled |
config-change-audit |
config_change_audit_enabled |
instructions-loaded-audit |
instructions_loaded_audit_enabled |
permission-denied-audit |
permission_denied_audit_enabled |
pre-compact-audit |
pre_compact_audit_enabled |
skill-usage-audit (both paths) |
skill_usage_audit_enabled |
tool-failure-audit |
tool_failure_audit_enabled |
hook-failure-audit |
hook_failure_audit_enabled |
instructions-loaded-audit drops deterministic, high-volume session_start
loads by default; set instructions_loaded_audit_log_session_start=true to opt
back into logging them. A stdin_read_timeout option (seconds, default 2) is
an idle bound on reading each hook's payload: any byte arriving resets it, so
a large or slowly-delivered payload is never cut off while it is still coming,
and it fires only once the pipe has gone silent for that long, at which point
these audit hooks fail open (skip). On a shell whose read -t accepts fractional
values the bound is read in four slices, so the stall is detected within a
quarter of the configured interval of it; where fractional timeouts are
unavailable (Bash 3.2, the macOS system shell) it is read as one window and a
producer that sends bytes then goes silent can take up to two intervals. A producer
that keeps emitting is bounded by Claude Code's own hook timeout, not by this
value. A setting this shell's read -t will not accept, or 0, falls back to
the default.
Set them interactively with /plugin configure claude-ops@<marketplace>, or headless on the
install command:
claude plugin install claude-ops@<marketplace> --config skill_usage_audit_enabled=falseOption scoping (user vs project settings, and the per-repository escape hatch) per "How to set these" below.
A migrated emitter is inert without a consumer. hooks/hook-telemetry-sink.sh
is a reference sink: it reads an envelope on stdin and appends one line under
the hook log root, <project-root>/.observability/claude by default (the
session_event_log_dir option moves it). An envelope carrying
data.session_id lands in sessions/<session_id>.jsonl, beside the
per-session event log; one without lands in the shared hook-events.jsonl
in the legacy shape. Both are what the observability skill reads. The root
carries a self-ignoring .gitignore, created on the first write (or by
/claude-ops:setup apply); rows left at the old
.claude/observability/hook-events.jsonl location are detected by setup as
retirement claude-ops-r001 and migrated on request.
Wire it by pointing HOOK_TELEMETRY_SINK at an executable that exists at
resolution time. A relative value resolves against the consuming repo
root, not the plugin cache, so the marketplace-installed copy under
${CLAUDE_PLUGIN_ROOT} is not reachable by a relative path (and Claude Code
injects settings.json env values literally, with no ${CLAUDE_PLUGIN_ROOT}
expansion). Two workable forms:
-
Copy the reference sink into your repo (e.g.
.claude/hooks/hook-telemetry-sink.sh) and wire that repo-relative path, the portable, team-shared, clone-safe form:{ "env": { "HOOK_TELEMETRY_SINK": ".claude/hooks/hook-telemetry-sink.sh" } }The sink
sourceshook-utils.shfrom its own directory, so a bare copy fails at startup: either copyhook-utils.shalongside it, or edit the copy'ssourceline to point at ahook-utils.shyour repo already carries. -
Or point at an absolute path to the installed sink under your plugin cache. It is per-machine, and it moves on each plugin update, so it is not clone-portable.
Any envelope producer (this plugin's hooks, guardrails, the formatters) then flows into the same store. The sink is fire-and-forget and best-effort, a slow or absent sink silently drops the event; it is for observability, not audit-of-record.
Independently of any sink, session_event_log_enabled=true turns on one
producer row per observable hook event (30 events; the generated
hooks/hook-events.registry.json says which, and why WorktreeCreate,
MessageDisplay and FileChanged are left out). Each fire appends one line to
<root>/sessions/<session_id>.jsonl: the correlation keys the payload carries
(prompt_id, tool_use_id, agent_id), the event and its category, the tool
and a repo-relative file path when present. A consumer who has not turned it on
pays the kill-switch read and nothing else (2.42 ms against a 2.08 ms spawn
floor on the Linux CI host); enabled, a 2 KB payload costs about 5 ms and a
512 KB one 36 ms. Windows Git Bash, the host the hook-budget convention binds
to, is unmeasured for these rows: the parallel-wall figure there, and the
budget comparison it feeds, are owed before the switch is recommended on by
default, and the default stays off until they are taken. session_event_log_categories narrows the set. At
SessionEnd the retention hook keeps the newest session_log_keep_sessions
or the last session_log_keep_days days, and session_log_pre_prune_command
hands an archiver the files about to go. The root carries its own *
.gitignore, so nothing under it reaches git status; /claude-ops:setup
reports the toggles and the guard, /claude-ops:observability session reads
the result.
/plugin marketplace add melodic-software/claude-code-plugins
/plugin install claude-ops@<marketplace>The defaults are repo-agnostic and everything project-specific routes through your own repository's context:
- Observability data locations. The OTEL store defaults to
<project-root>/.claude/observability/oteland is overridable via theCC_OTEL_STOREenv var (retention windows viaCC_OTEL_RETENTION_DAYS/CC_OTEL_BODY_RETENTION_DAYS). The hook-event JSONL source is read from the hook log root (<project-root>/.observability/claudeby default, thesession_event_log_diroption moves it):sessions/<session_id>.jsonlwhen the per-session event log is on or the sink is wired, and the sharedhook-events.jsonlfor envelopes without a session id; every source degrades gracefully when absent. - Persistent state defaults to the plugin's own per-machine data directory
(
${CLAUDE_PLUGIN_DATA}): the known-issues registry (registry.json),check-alloutput,--writeobservability reports, and thelanesskill's per-lane launch-commit markers (${CLAUDE_PLUGIN_DATA}/lanes/<repo-key>/<lane>-launch-commit, overridable vialane-launcher.sh --data-dir).<repo-key>namespaces markers by repository, the data directory is plugin-wide, while a lane name likeworkis only unique within one checkout.<repo-key>is a digest of the repository's canonical path; print the one for a given checkout withprintf '%s' "$(git rev-parse --show-toplevel)" | git hash-object --stdin. By default nothing is written into your repository. Opt in for the registry via theregistry_diroption (see Configuration) to keep it git-tracked and team-shared inside your repo instead. - Work-item and docs integration. Where the skills propose follow-up work
items or cross-reference quirks/workaround docs, they use whatever tracker
and docs your project has (e.g.
gh issue create, yourCLAUDE.md/.claude/rules) and skip silently when there is none.
The audit hooks are Bash scripts (Git Bash on native Windows, so install
Git for Windows) and
use jq; without jq they fail open (no audit line is written).
audit-install-state needs Python 3.11+ only. No PowerShell, no third-party packages, no
jq. Its inventory, surface classification, filename-scheme resolution, retention resolution and
sampling are pure os.walk + stat + regex and behave identically on every platform. The single
OS-specific seam is one function, probe_pid(), with a POSIX body (os.kill(pid, 0)) and a Windows
body (OpenProcess); a probe that cannot run reports unverified, never dead.
Core flows need only git, jq, gh (authenticated), and python3.
Optional: duckdb for OTEL store queries and npx for ccusage. The machine-level
otelcol-contrib service and Aspire dashboard Compose stack are provisioned separately; the
plugin observes them but does not start them. Every skill reports missing optional tooling
instead of failing.
The per-hook kill switches, instructions_loaded_audit_log_session_start, and
stdin_read_timeout are documented under
Per-hook kill switches. Three further userConfig
options tune the skills:
install_new(string, optional). New-catalog-plugin install policy for thepluginsskill'ssyncaction.ask(default) offers not-yet-installed catalog plugins in one batched multi-select prompt;allinstalls every one automatically;nonereports them without installing. The manifest schema has noenumtype, so this validates in prose, not JSON Schema; any other value is treated asask.registry_dir(string, optional). Project-relative directory for the known-issues registry (registry.json). Set it to keep the registry inside your repo (git-tracked, team-shared) instead of the per-machine plugin data directory; leave unset to use${CLAUDE_PLUGIN_DATA}. Absolute, drive-qualified, UNC, traversal, and escaping-symlink paths are invalid; known-issues operations must stop and direct you to reconfigure rather than write outside the project.skill_usage_scope(string, optional), where theskill-usage-auditsecond store lives.repo(default) keeps it in the project tree, with a machine-local.git/info/excludeentry sogit statusstays clean;userresolves the sameskill_usage_dirsubpath under$HOMEfor one cross-repo operator store (rows carryproject+ collision-resistantproject_idfields);data-dirwrites${CLAUDE_PLUGIN_DATA}/skill-usage/<repo-slug>. Plugin-owned, update-safe, never in any repo tree. Prose-validated (noenumin the manifest schema); an unknown value falls back torepowith a one-time advisory. The default staysrepodeliberately: the store stays in the project tree, matching the observability posture that telemetry is project-local, and the exclude entry removes the status noise that motivated the scope knob. (The hook log root is a separate tree with its own self-ignoring guard; see "Wiring the reference sink".)skill_usage_git_exclude(boolean, defaulttrue). Repo scope only: idempotently exclude the store dir via.git/info/exclude(never touches.gitignoreor tracked files). Setfalsewhen your team deliberately commits the telemetry.skill_usage_dir(string, optional). Contained relative directory, resolved under the scope root (repo scope: repo root; user scope:$HOME), whereskill-usage-auditwrites itsskill-usage.jsonlsecond store (the "measuring skills" record, separate from the telemetry envelope); leave unset to use.claude/observability. The same containment rules apply in every scope; thedata-dirscope ignores it. An invalid value produces a visible advisory and skips the second-store write; telemetry through an independently configured sink can still proceed.
Run /claude-ops:setup to validate this choice. Claude Code owns persistence through its plugin
configuration prompt; rerun setup afterward to verify the rendered value.
Remaining variability is covered by the env vars above and conventional
project-relative defaults; the bundled scripts make no outbound network calls
except gh/curl reads of GitHub and Claude status pages in the
known-issues skill.
Generated from this plugin's .claude-plugin/plugin.json. Every option Claude Code
will prompt for when the plugin is enabled, with the environment variable each hook
reads it from.
| Option | Type | Default | Environment variable | Description |
|---|---|---|---|---|
registry_dir |
string | (none) | CLAUDE_PLUGIN_OPTION_REGISTRY_DIR |
Optional contained project-relative directory holding the known-issues registry (registry.json). Absolute, drive, UNC, traversal, and escaping-symlink paths are invalid. Leave unset to use ${CLAUDE_PLUGIN_DATA}. |
skill_usage_dir |
string | (none) | CLAUDE_PLUGIN_OPTION_SKILL_USAGE_DIR |
Optional contained relative directory where the skill-usage-audit hooks write skill-usage.jsonl, resolved under the skill_usage_scope root (repo scope: the project root; user scope: $HOME). Absolute, drive, UNC, traversal, and escaping-symlink paths are invalid in every scope. Ignored by the data-dir scope (plugin-owned layout). Leave unset to use .claude/observability. |
skill_usage_scope |
string | "repo" |
CLAUDE_PLUGIN_OPTION_SKILL_USAGE_SCOPE |
Where the skill-usage store lives. Valid values: "repo" (the default, a project tree under the repo root, kept out of git status via a machine-local .git/info/exclude entry), "user" (the skill_usage_dir subpath under |
skill_usage_git_exclude |
boolean | true |
CLAUDE_PLUGIN_OPTION_SKILL_USAGE_GIT_EXCLUDE |
When the repo-scope store sits inside a git work tree, idempotently add its directory to .git/info/exclude (machine-local; never touches .gitignore or tracked files) so git status stays clean. Set false if your team deliberately commits the telemetry. |
install_new |
string | "ask" |
CLAUDE_PLUGIN_OPTION_INSTALL_NEW |
Controls what sync does with catalog plugins that aren't installed yet. Valid values: "ask" (the default, which offers them in one batched multi-select prompt), "all" (install every one automatically), "none" (report only, never install). The manifest schema has no enum type, so this validates in prose, not JSON Schema; any other value is treated as "ask". |
api_error_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_API_ERROR_AUDIT_ENABLED |
Emit turn-failure telemetry on API errors |
config_change_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_CONFIG_CHANGE_AUDIT_ENABLED |
Emit telemetry on config-source mutations |
instructions_loaded_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_INSTRUCTIONS_LOADED_AUDIT_ENABLED |
Emit telemetry on rule/instruction file loads |
permission_denied_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_PERMISSION_DENIED_AUDIT_ENABLED |
Emit telemetry on permission denials |
pre_compact_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_PRE_COMPACT_AUDIT_ENABLED |
Emit telemetry on context-compaction events |
skill_usage_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_SKILL_USAGE_AUDIT_ENABLED |
Emit telemetry on skill usage; shared by both skill-usage audit hooks (the Skill-tool and slash-command expansion paths) and also gates the shared skill-usage.jsonl store |
tool_failure_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_TOOL_FAILURE_AUDIT_ENABLED |
Emit telemetry on Write/Edit/Bash tool failures |
hook_failure_audit_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_HOOK_FAILURE_AUDIT_ENABLED |
Warn once per session per hook when the transcript records hook launch/exec failures Claude Code never surfaced |
instructions_loaded_audit_log_session_start |
boolean | false |
CLAUDE_PLUGIN_OPTION_INSTRUCTIONS_LOADED_AUDIT_LOG_SESSION_START |
Opt back into logging session_start instruction loads (dropped by default as deterministic and high-volume) |
stdin_read_timeout |
number min 1 |
2 |
CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT |
Idle bound on reading the hook payload from stdin: how long the pipe may go silent before the hook gives up and fails open |
session_event_log_enabled |
boolean | false |
CLAUDE_PLUGIN_OPTION_SESSION_EVENT_LOG_ENABLED |
Append one JSON line per hook event to <session_event_log_dir>/sessions/<session_id>.jsonl, on every documented event the generated registry marks observable. Off by default: a consumer who has not turned it on pays the kill-switch read and nothing else. The same switch gates the SessionEnd retention hook. |
session_event_log_dir |
string | ".observability/claude" |
CLAUDE_PLUGIN_OPTION_SESSION_EVENT_LOG_DIR |
Contained project-relative directory holding the per-session hook event log (sessions/) and the telemetry sink's hook-events.jsonl. Absolute, drive, UNC, traversal and escaping paths are invalid, and the project root itself is refused. Inside a checkout the directory carries a self-ignoring .gitignore, created on the first write. Leave unset to use .observability/claude. |
session_event_log_categories |
string | (none) | CLAUDE_PLUGIN_OPTION_SESSION_EVENT_LOG_CATEGORIES |
Comma-separated event categories to record (session, prompt, tool, permission, agent, task, turn, config, worktree, compaction, model, mcp, display, other). Empty records every category the registry marks observable. |
session_log_keep_sessions |
number min 1 |
30 |
CLAUDE_PLUGIN_OPTION_SESSION_LOG_KEEP_SESSIONS |
At SessionEnd, keep the newest N session files regardless of age (a file is kept when it is among the newest N OR younger than session_log_keep_days). |
session_log_keep_days |
number min 1 |
14 |
CLAUDE_PLUGIN_OPTION_SESSION_LOG_KEEP_DAYS |
At SessionEnd, keep every session file younger than N days regardless of count (a file is kept when it is younger than N days OR among the newest session_log_keep_sessions). |
session_log_pre_prune_command |
string | (none) | CLAUDE_PLUGIN_OPTION_SESSION_LOG_PRE_PRUNE_COMMAND |
Optional command run detached at SessionEnd with one argument, a directory the session files about to be pruned were moved into; the physical delete of that directory happens on the next retention run after 24 hours, so an archiver has a stable set to read. Executed through bash -c, so it is trusted configuration: on current releases project and local pluginConfigs are ignored and only the user's own settings supply it (recheck: the plugins reference's user-configuration section). Leave unset to delete directly. |
Three supported routes, in the order most people want them:
-
Interactively. Claude Code prompts for declared options when you enable the plugin. To change them later:
/plugin configure claude-ops@<marketplace>. -
Headless. Repeat
--configfor each option. Replace<marketplace>with the marketplace you installed this plugin from:claude plugin install claude-ops@<marketplace> -s <scope> --config registry_dir=<value>
The same command reconfigures a plugin that is already installed: it prints
already installedand still writes the value. The short-circuit message is about the install, not the config write. Do notclaude plugin uninstallto reconfigure: uninstalling drops this plugin's whole storedpluginConfigsentry, resetting every option in the table above to its default.-sdefaults touser, so pass the scopeclaude plugin listreports for this plugin. The verified-version record lives in the plugin-reconfiguration convention.The value is stored immediately; the session you are in does not change. Hooks are handed their
CLAUDE_PLUGIN_OPTION_*when the session starts, so start a fresh Claude Code session before expecting new behavior. A check run in the old session still reports the old value, and that is not a failed write. -
By hand, in settings. Add the value under
pluginConfigsin your user settings (~/.claude/settings.json):{ "pluginConfigs": { "claude-ops@<marketplace>": { "options": { "registry_dir": <value> } } } }Plugin option values are read from user,
--settings, and managed settings only, not from a project's.claude/settings.json. To vary behavior per repository, enable or disable the plugin in that project'senabledPluginsinstead of setting an option there.
Do not set the CLAUDE_PLUGIN_OPTION_* variables yourself. They are how Claude Code
hands a configured value to a hook process; the value comes from the routes above.
- User configuration: the
userConfigschema and theCLAUDE_PLUGIN_OPTION_<KEY>export - Plugin install options: the
--configflag's reference entry - Plugins and skills settings:
enabledPlugins,extraKnownMarketplaces,pluginConfigs - Settings files and who they affect: user vs project vs local precedence
- Manage installed plugins: enabling, disabling,
/plugin list
MIT (SPDX-License-Identifier: MIT).