`plugin/` is not built into the image; installs fetch it from this repo via
.claude-plugin/marketplace.json, so a push IS the release. It was absent
from the workflow's `paths:` filter entirely, meaning plugin changes ran no
CI at all. Two separate defects reached a live install through that gap:
#2198 — all four hook scripts inert (lowercase userConfig env vars,
line-oriented `jq -rR`, line-oriented `cut -c`)
#2209 — the fix for #2198 couldn't reach an install because the manifest
version wasn't bumped, so the installer never refreshed its cache
Adds `plugin/**` + `.claude-plugin/**` to `paths:` and a `plugin` job
running scripts/check_plugin.py:
1. `bash -n` on every hook.
2. The three known-bad patterns from #2198. Verified by replay against
c569cdd^ — all three are caught. Narrow by design; see below.
3. Shipped plugin content differs from origin/main => the manifest version
must differ too. Stated against the base branch, not per-commit, so a
batch needs one bump rather than one per commit. Replayed against
c569cdd: correctly fails.
The checker found a real outstanding bug on its first run: the `cut -c1-2000`
prompt cap in scribe_autoinject.sh was still line-oriented. Only the
prior-art hook's copy got fixed in c569cdd. Now `head -c`. It then failed
on this very commit for a missing version bump, which is the third time
that rule has mattered and the first time something other than memory
enforced it. Manifest bumped to 0.1.20.
WHAT THIS DOESN'T COVER, and why. shellcheck is the right tool for check 2
and is NOT in ci-python; nor is jq, which every hook requires and silently
bails without — so a "runs and stays silent" smoke test would pass
vacuously today and prove nothing. Both need those two packages added to
the CI image in the CI-runner repo, which is a separate change to a
separate repo (rule #5: the toolchain comes from the image, not from
apt-get at job start). Verified against CI-runner's Dockerfile and
scripts/install-common.sh rather than assumed (rule #37).
`plugin` is not in the build job's `needs`: the plugin doesn't ship in the
image, and blocking the build wouldn't un-publish a bad hook — the push
already did. A failed job still reddens the run.
Closes#2204
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
Three defects, each of which independently made a hook a no-op, and all
three failing silently — which is why the whole dynamic side of the plugin
looked "shipped" while doing nothing.
1. Wrong env var case. Claude Code exports userConfig to hooks as
CLAUDE_PLUGIN_OPTION_<KEY> with the key UPPERCASED. All four hooks read
CLAUDE_PLUGIN_OPTION_api_endpoint / _api_token, so both values were
always empty. That killed the SessionStart dynamic tier, process sync,
prompt auto-inject, and the write-path prior-art trigger at once.
2. jq -rR is line-oriented. `@uri` under -R encodes input LINE BY LINE, so
a multi-line payload came back as several encoded lines joined by raw
newlines — an invalid URL, curl fails, hook exits 0 in silence. Now
-sRr. This one hid behind (1): auto-inject only ever worked for
single-line prompts, and prior-art (which posts code, always
multi-line) could never have worked at all.
3. cut -c1-1200 caps each LINE, not the payload, so the prior-art code
budget wasn't a budget. Now head -c 1200.
Also widens the SessionStart warning: "neither URL nor token arrived" used
to be treated as a benign unconfigured install and stayed quiet. That is
exactly the state defect (1) produced, so the one install state that most
needed a signal was the only one that emitted none. It now says so, and
names the two other features it silently disables.
Verified against the live instance: dynamic rules + project context load,
auto-inject surfaces #2192 on a multi-line prompt, and the write-path
trigger returns the [here] place-arm hit on a multi-line edit with session
dedup suppressing the repeat.
Refs #2198, #2082
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
New UserPromptSubmit hook (scribe_autoinject.sh) + GET /api/plugin/retrieve that
surface the TITLES (never bodies) of the few notes clearing four anti-bloat
gates: a per-user confidence threshold (stricter than pull search), a margin
gate, per-session dedup (exclude_ids), and a top-k ceiling. Each retrieval is
logged to retrieval_logs as source=auto_inject so the threshold can be tuned
from data. Per-user config (enable / threshold / top-k) is DB-backed via
/api/settings with a Settings UI card; defaults enabled, threshold 0.55,
top-k 3 (conservative — tune once auto_inject telemetry accrues).
Scribe: project 2, milestone 93, task 1033.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E