fix(plugin): stop CI installing the jq the hooks no longer use (#4107)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 42s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m30s
CI & Build / Build & push image (push) Successful in 23s

Three loose ends from a49e7ed, all found by CI or by re-reading it.

TWO TESTS PINNED THE jq SYNTAX, not the word `jq`, so grepping the suite for
the tool name missed them: test_write_path_trigger asserted the literal filter
strings `(.note_ids // []) - (.sync_note_ids // [])` and `(.rule_ids // [])[]?`
appear in the hook. Re-spelled with the readers that replaced them. The claim
each makes is unchanged — that the reuse and sync channels keep their own
state file, their own query parameter and their own write-back.

CI STILL INSTALLED jq, TWICE. The "Install jq for hook execution tests" step
existed because those tests skipped rather than failed without it; the Plugin
hooks job installed it alongside shellcheck for the same reason. Removing a
dependency from the product and leaving CI to install it anyway means the
smoke tests never run under the condition they exist to assert. Both gone —
the hook tests now run on a bare image.

`awk 'END { print $0 }'` IS UNDEFINED IN POSIX. gawk retains the last record
in END and mawk does too, but it is not guaranteed, and reaching for a
non-portable idiom inside the change whose entire purpose is portability is
the same mistake one layer down. Now `{ last = $0 } END { if (NR) print last }`.
Verified it still selects the same start line on a real transcript.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-20 12:23:42 -04:00
co-authored by Claude Opus 5
parent a49e7ed2af
commit 4bfa0cbbc0
4 changed files with 19 additions and 19 deletions
+13 -14
View File
@@ -111,7 +111,7 @@ jobs:
# Guards the one part of this repo that ships to users without a build step. # Guards the one part of this repo that ships to users without a build step.
# See scripts/check_plugin.py for what it checks and, as importantly, what it # See scripts/check_plugin.py for what it checks and, as importantly, what it
# can't check yet (shellcheck and jq are absent from ci-python). # can't check yet (shellcheck is absent from ci-python).
plugin: plugin:
name: Plugin hooks name: Plugin hooks
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
@@ -133,12 +133,15 @@ jobs:
# the only consumer today. Promotion into ci-python is filed as an issue # the only consumer today. Promotion into ci-python is filed as an issue
# on CI-runner rather than assumed here. # on CI-runner rather than assumed here.
# #
# jq is not optional for the smoke test: every hook exits at line 1 # jq WAS installed here too, because every hook exited at line 1 without
# without it, so the check would pass while exercising nothing. # it and the smoke test would have passed while exercising nothing. The
# hooks need no jq since #4107 — they use only what POSIX guarantees — so
# the smoke test now runs on a bare image, which is the condition it is
# actually meant to be asserting.
- name: Install shell tooling - name: Install shell tooling
run: | run: |
apt-get update -qq apt-get update -qq
apt-get install -y -qq --no-install-recommends jq shellcheck apt-get install -y -qq --no-install-recommends shellcheck
# On main the comparison would be against itself, so only the syntax and # On main the comparison would be against itself, so only the syntax and
# pattern checks mean anything there. # pattern checks mean anything there.
@@ -223,16 +226,12 @@ jobs:
UV_PROJECT_ENVIRONMENT: /opt/venv UV_PROJECT_ENVIRONMENT: /opt/venv
run: uv sync --locked --extra dev run: uv sync --locked --extra dev
# The hook-EXECUTION tests (test_write_path_trigger's nudge pair) run the # An "Install jq for hook execution tests" step stood here. The hook
# real bash hook, which exits silently without jq — and those tests skip # EXECUTION tests run the real bash hook, which exited silently without
# rather than fail when it's absent, so without this step they would # jq and skipped rather than failed, so the step existed to stop them
# quietly never be verified anywhere (ci-python ships without jq; same # being silently unverified. Since #4107 the hooks need no jq, so the
# install the Plugin hooks job does). # step is gone and those tests run against a bare image — and the point
- name: Install jq for hook execution tests # of removing a dependency is lost if CI keeps installing it anyway.
run: |
apt-get update -qq
apt-get install -y -qq --no-install-recommends jq
- name: Run tests - name: Run tests
# Integration tests (real Postgres) run in the `integration` job below. # Integration tests (real Postgres) run in the `integration` job below.
run: /opt/venv/bin/python -m pytest tests/ -q -m "not integration" run: /opt/venv/bin/python -m pytest tests/ -q -m "not integration"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "scribe", "name": "scribe",
"description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).", "description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).",
"version": "2026.09.20.1620", "version": "2026.09.20.1623",
"author": { "author": {
"name": "Bryan Van Deusen" "name": "Bryan Van Deusen"
}, },
+1 -1
View File
@@ -119,7 +119,7 @@ turn_facts() { window | tail -n +"${1:-1}" | scribe_json_flat_lines \
# slow way — correct, and slow, which is the right way round for a check that # slow way — correct, and slow, which is the right way round for a check that
# can block a stop. # can block a stop.
start=$(window | grep -n -F '"message":{"role":"user","content":"' 2>/dev/null \ start=$(window | grep -n -F '"message":{"role":"user","content":"' 2>/dev/null \
| cut -d: -f1 | awk 'END { if (NR) print $0 }') | cut -d: -f1 | awk '{ last = $0 } END { if (NR) print last }')
case "$start" in ''|*[!0-9]*) start=1 ;; esac case "$start" in ''|*[!0-9]*) start=1 ;; esac
facts=$(turn_facts "$start") facts=$(turn_facts "$start")
+4 -3
View File
@@ -1091,8 +1091,9 @@ def test_hook_keeps_sync_and_reuse_dedup_apart():
assert ".sync.ids" in src # its own state file assert ".sync.ids" in src # its own state file
assert "exclude_sync_ids=" in src # its own query channel assert "exclude_sync_ids=" in src # its own query channel
# The reuse file must NOT swallow sync ids — the write-back subtracts them. # The reuse file must NOT swallow sync ids — the write-back subtracts them.
assert "(.note_ids // []) - (.sync_note_ids // [])" in src # Spelled with the readers that replaced jq (#4107); the claim is unchanged.
assert "(.sync_note_ids // [])[]?" in src assert "scribe_json_list_minus \"$body_flat\" '.note_ids' '.sync_note_ids'" in src
assert "scribe_json_list \"$body_flat\" '.sync_note_ids'" in src
def _hook_runtime_env(): def _hook_runtime_env():
@@ -1668,7 +1669,7 @@ def test_hook_keeps_the_rule_channel_apart_from_the_other_three():
src = HOOK.read_text() src = HOOK.read_text()
assert ".rules.ids" in src # its own state file assert ".rules.ids" in src # its own state file
assert "exclude_rule_ids=" in src # its own query channel assert "exclude_rule_ids=" in src # its own query channel
assert "(.rule_ids // [])[]?" in src # its own write-back assert "scribe_json_list \"$body_flat\" '.rule_ids'" in src # its own write-back
# And it rides the same request as the rest, not a second round trip. # And it rides the same request as the rest, not a second round trip.
assert "${rule_exclude_q}" in src assert "${rule_exclude_q}" in src