Six commits from dev. They finish milestone 410 "One owner per piece of guidance" (step 8) and build steps 4–5 of milestone 409 "Response shapes — replies say where the work stands", with two hook fixes found along the way.
Guidance
using-scribe states the retrieval reflex once (#4039). "Rules arrive by retrieval; ask before a consequential act; an empty session isn't an empty rulebook" now lives in reflex 2 alone. The adapter's session-start text no longer restates it.
Response shapes (milestone 409)
An operator's own reply shapes reach the reply they're about (#4013).
Closing a task with update_task returns matching preferences as reply_preferences. It uses a kind-filtered preference search keyed on each preference's own trigger and logs under report_preference in retrieval_logs.
For every other kind of reply, the reporting-back skill has the agent search for its shape preferences before writing.
A Stop hook checks completion reports (#4014). When a turn closes a task, scribe_report_check.sh checks the final reply says where the work sits, what needs the operator, and what comes next.
If any of those are missing, it sends the reply back once, using the reason returned by the new GET /api/plugin/report-check.
It blocks only when the block was recorded, so an unconfigured or unreachable instance never blocks.
Outcomes go to app_logs (category plugin, action report_check) for the milestone's step 6 to read.
Fixes
Stop hook prefilter (#4041). tail | grep -q under pipefail failed exactly when it matched. Test transcripts also weren't compact JSON.
Duplicate check wiped its own output (#4042). Under pipefail, $(… | head -N) || v="" discarded head's output when the producer was still writing, so the by-name duplicate arm went silent for heavily duplicated names. Fixed at five sites, and check_plugin now carries a known-bad pattern for the shape.
Goal line cut at a word (#4036). The session-start Goal line now cuts at a word and says where the rest is.
Verification
CI green on the dev head 0fab082 (run #519): lint, typecheck, plugin hooks, Python tests, integration, image build.
Six commits from dev. They finish milestone 410 "One owner per piece of guidance" (step 8) and build steps 4–5 of milestone 409 "Response shapes — replies say where the work stands", with two hook fixes found along the way.
## Guidance
- **using-scribe states the retrieval reflex once** (#4039). "Rules arrive by retrieval; ask before a consequential act; an empty session isn't an empty rulebook" now lives in reflex 2 alone. The adapter's session-start text no longer restates it.
## Response shapes (milestone 409)
- **An operator's own reply shapes reach the reply they're about** (#4013).
- Closing a task with `update_task` returns matching preferences as `reply_preferences`. It uses a kind-filtered preference search keyed on each preference's own trigger and logs under `report_preference` in retrieval_logs.
- For every other kind of reply, the reporting-back skill has the agent search for its shape preferences before writing.
- **A Stop hook checks completion reports** (#4014). When a turn closes a task, `scribe_report_check.sh` checks the final reply says where the work sits, what needs the operator, and what comes next.
- If any of those are missing, it sends the reply back once, using the reason returned by the new `GET /api/plugin/report-check`.
- It blocks only when the block was recorded, so an unconfigured or unreachable instance never blocks.
- Outcomes go to app_logs (category `plugin`, action `report_check`) for the milestone's step 6 to read.
## Fixes
- **Stop hook prefilter** (#4041). `tail | grep -q` under pipefail failed exactly when it matched. Test transcripts also weren't compact JSON.
- **Duplicate check wiped its own output** (#4042). Under pipefail, `$(… | head -N) || v=""` discarded head's output when the producer was still writing, so the by-name duplicate arm went silent for heavily duplicated names. Fixed at five sites, and check_plugin now carries a known-bad pattern for the shape.
- **Goal line cut at a word** (#4036). The session-start Goal line now cuts at a word and says where the rest is.
## Verification
- CI green on the dev head `0fab082` (run #519): lint, typecheck, plugin hooks, Python tests, integration, image build.
- Plugin version minted: `2026.09.14.2254`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A raw 200-char slice ended mid-word with nothing marking the cut, so a
reader took half a sentence for the whole goal. _goal_line flattens the
goal to one line, trims at a word break with an ellipsis, and points at
enter_project(id) when it cut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 7's live check (#4034 "a real session shows each topic stated once")
found nothing copied across surfaces, but found the owner repeating itself:
using-scribe said "rules are retrieved; ask before a consequential act; an
empty session is not an empty rulebook" in "Do this first", in reflex 2, and
twice more further down reflex 2.
- using-scribe: reflex 2 now holds the one full statement (how rules arrive,
why that is the surface working, what silence means, when to ask). "Do this
first" keeps the enter_project step and a short lead-in pointing at reflex
2. "A retrieved rule outranks a default habit" and "Ask hardest where you
feel most certain" stay, since each adds something.
- Adapter static context: "Lines injected beside your work" keeps only the
Claude Code timing and leaves "never the whole set" to the skill. "Keep one
copy" names CLAUDE.md and auto-memory and points at using-scribe, which
already says to leave a client's settings as they are.
- The ownership registry's markers and statement for the topic still sit on
using-scribe, and nothing else changed owner. Plugin version minted.
Sizes: using-scribe 18,823 -> 18,197 chars; scribe_static_context.md
1,797 -> 1,714.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reporting-back skill ships default shapes; an operator's adjustments to
them are preference records. Prompt-time retrieval matches the operator's
message, and a shape preference is about the reply, so those preferences were
on file and never arrived. Operator's decision (logged on #4013): the server
delivers them for a completion report, and the skill asks for every other kind.
- Completion reports (option C): closing a task with update_task runs a
kind-filtered preference search for the moment "writing the completion
report after finishing a task" and returns matches as `reply_preferences`
({id, title, statement, kind}), with a sentence added to `report_back`
naming the key. A preference says it is about completion reports through
its own when_to_apply; no tag or column. Omitted when nothing matches, and
the lookup fails open.
- Telemetry: every call logs to retrieval_logs under `report_preference`
(empty calls included; a search that never ran writes no row) and hits are
recorded surfaced. The source is ranked, so it counts toward pull-through.
The bar is the prompt arm's setting until step 6 reads this source's near
misses.
- Every other reply (option A): reporting-back gains "The operator's own
shapes come first". Before a finding, decision, handoff or "where are we",
search(content_type="rule") in the words of that moment and follow what
comes back. Registered in the ownership guard with reporting-back as owner.
- Loading reply shapes at session start (option B) was rejected: it would be
a small copy of the preloading milestone 394 retired.
Domain-neutral query (pinned); works on an install with no preferences.
Plugin version minted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything else Scribe gives an agent arrives before the reply is written. A
Stop hook is the one moment the finished reply exists, so it is the last
chance to fix a report the operator can't read, and the only place adherence
to the shape can be measured.
- plugin/hooks/scribe_report_check.sh (Stop): deterministic, no model call.
1. Did this turn close a task? That means an update_task/create_task call
with status "done" since the turn's prompt, whose tool_result is not an
error. Otherwise it stays silent, which covers most turns (one grep).
2. Does the reply that ends the turn say where the work sits (a record by
id and title, or step N of M), what needs the operator, and what comes
next? Matched on those words, not on exact headings.
3. If sections are missing, it blocks once. With stop_hook_active set, a
rewrite is recorded (passed_after_rewrite / missing_after_rewrite) and
never blocked again. A block loop started by another plugin (no marker
from this hook) is left alone.
- Measured: every checked reply is reported to GET /api/plugin/report-check
(passed / blocked / after rewrite). Turns that close nothing are not
reported; they would cost a request per turn and add nothing to the rate.
Outcomes go to app_logs as category "plugin", action "report_check".
- It blocks only when the block was recorded, and only in the server's words.
The endpoint returns the block reason, so the hook carries timing and
transport only (PACKAGING.md), and an unconfigured or unreachable instance
never stops a session.
- The transcript format is read from real transcripts and marked in the hook
as observed rather than documented. The Stop contract (transcript_path,
stop_hook_active, decision/reason, no matcher, SubagentStop separate) was
checked against the Claude Code hooks docs. A prompt-type hook was not
needed: the deterministic check passed a real completion report from this
session and blocked a stripped one.
- A pipefail trap was caught while exercising the hook: `tail | grep -q`
reports failure exactly when grep matches, because tail dies of SIGPIPE.
The prefilter reads through process substitution; the section checks use
here-strings.
- Tests: an end-to-end hook suite over synthetic transcripts and the shared
HTTP sink (silence, pass, server-worded block, rewrite recorded, foreign
loop, errored write, earlier turn, unwritten reply, no recorded check, bare
id), and service tests for the reason wording and the outcome record. Smoke
event added to check_plugin; README and PACKAGING list the hook and
endpoint. Plugin version minted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI run #517 failed five of the new hook tests, all of them expecting a
request that never went out. The prefilter matched `"name":"…update_task"`
with no space after the colon, which is how Claude Code writes its
transcripts, while the tests wrote theirs with json.dumps defaults
(`"name": "…"`). The hook exited at the prefilter for every test transcript.
The silent-case tests passed for the same wrong reason.
- The prefilter now allows whitespace after the colon. The jq parse behind it
never depended on formatting.
- The test transcripts are written compact, matching the real file. The
silent-case tests now reach the turn parse rather than stopping at the
prefilter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In a repo where 3,000 files define `slug`, scribe_local_dups printed
nothing; with 3 files it printed the duplicate line. Every hook runs under
`set -uo pipefail`, and `hits=$(git grep -l … | head -4) || hits=""` lost
head's four lines whenever git grep was still writing when head exited. That
is a SIGPIPE, the substitution fails, and the outer fallback wipes the result.
So the by-name duplicate arm went silent for exactly the most-duplicated
names. Found while fixing the same trap in the new Stop hook (#4041).
- The fallback moves inside the substitution, `$(… | head -N || true)`, at all
five sites: scribe_defs.sh (local dups), scribe_prior_art.sh (names,
old_first, shapes) and scribe_after_write.sh (names). A real upstream
failure still yields empty output.
- check_plugin gains a known-bad pattern for the shape, so no hook can bring
it back.
- Tests: the real function against a 3,000-file repo (past the pipe buffer),
and the pattern shown to flag the old shape and pass the fix. Plugin
version minted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Six commits from dev. They finish milestone 410 "One owner per piece of guidance" (step 8) and build steps 4–5 of milestone 409 "Response shapes — replies say where the work stands", with two hook fixes found along the way.
Guidance
Response shapes (milestone 409)
update_taskreturns matching preferences asreply_preferences. It uses a kind-filtered preference search keyed on each preference's own trigger and logs underreport_preferencein retrieval_logs.scribe_report_check.shchecks the final reply says where the work sits, what needs the operator, and what comes next.GET /api/plugin/report-check.plugin, actionreport_check) for the milestone's step 6 to read.Fixes
tail | grep -qunder pipefail failed exactly when it matched. Test transcripts also weren't compact JSON.$(… | head -N) || v=""discarded head's output when the producer was still writing, so the by-name duplicate arm went silent for heavily duplicated names. Fixed at five sites, and check_plugin now carries a known-bad pattern for the shape.Verification
0fab082(run #519): lint, typecheck, plugin hooks, Python tests, integration, image build.2026.09.14.2254.🤖 Generated with Claude Code
Everything else Scribe gives an agent arrives before the reply is written. A Stop hook is the one moment the finished reply exists, so it is the last chance to fix a report the operator can't read, and the only place adherence to the shape can be measured. - plugin/hooks/scribe_report_check.sh (Stop): deterministic, no model call. 1. Did this turn close a task? That means an update_task/create_task call with status "done" since the turn's prompt, whose tool_result is not an error. Otherwise it stays silent, which covers most turns (one grep). 2. Does the reply that ends the turn say where the work sits (a record by id and title, or step N of M), what needs the operator, and what comes next? Matched on those words, not on exact headings. 3. If sections are missing, it blocks once. With stop_hook_active set, a rewrite is recorded (passed_after_rewrite / missing_after_rewrite) and never blocked again. A block loop started by another plugin (no marker from this hook) is left alone. - Measured: every checked reply is reported to GET /api/plugin/report-check (passed / blocked / after rewrite). Turns that close nothing are not reported; they would cost a request per turn and add nothing to the rate. Outcomes go to app_logs as category "plugin", action "report_check". - It blocks only when the block was recorded, and only in the server's words. The endpoint returns the block reason, so the hook carries timing and transport only (PACKAGING.md), and an unconfigured or unreachable instance never stops a session. - The transcript format is read from real transcripts and marked in the hook as observed rather than documented. The Stop contract (transcript_path, stop_hook_active, decision/reason, no matcher, SubagentStop separate) was checked against the Claude Code hooks docs. A prompt-type hook was not needed: the deterministic check passed a real completion report from this session and blocked a stripped one. - A pipefail trap was caught while exercising the hook: `tail | grep -q` reports failure exactly when grep matches, because tail dies of SIGPIPE. The prefilter reads through process substitution; the section checks use here-strings. - Tests: an end-to-end hook suite over synthetic transcripts and the shared HTTP sink (silence, pass, server-worded block, rewrite recorded, foreign loop, errored write, earlier turn, unwritten reply, no recorded check, bare id), and service tests for the reason wording and the outcome record. Smoke event added to check_plugin; README and PACKAGING list the hook and endpoint. Plugin version minted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>