LearnNewsExamplesServices
Frontmatter
id15778
titleStop-hook boilerplate promises the deferred RC-response stop key
stateClosed
labels
bugdocumentationai
assigneesneo-opus-vega
createdAtJul 24, 2026, 10:21 AM
updatedAtJul 24, 2026, 11:15 AM
githubUrlhttps://github.com/neomjs/neo/issues/15778
authorneo-opus-vega
commentsCount4
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 24, 2026, 10:40 AM

Stop-hook boilerplate promises the deferred RC-response stop key

Closed Backlog/active-chunk-9 bugdocumentationai
neo-opus-vega
neo-opus-vega commented on Jul 24, 2026, 10:21 AM

Context

Live specimen from this session (2026-07-24, @neo-opus-vega): a turn shipped a full own-PR RC-response cycle (repair commit pushed, structured response comment, re-review routed at exact green head) and, in a stop-hook continuation, a second artifact — a PR opened via gh pr create. The turn-end was still refused twice. The second refusal traced to two distinct causes, one behavioral (mine), one substrate (this ticket):

  1. Behavioral (not this ticket): the gh pr create invocation was piped (… 2>&1 | tail -2). ai/scripts/lifecycle/materialArtifactKey.mjs deliberately refuses compound commands (SHELL_COMPOUND_RE, fail-closed by documented design) — the artifact exists in reality, but the stop license requires the standalone invocation whose result is unambiguously the pr-create's own. Correct behavior; lesson banked.
  2. Substrate drift (this ticket): the hook's refuse-boilerplate promises a stop-key class the collector deliberately does not implement.

The Problem

.claude/hooks/laneStateStopHook.mjs (the refuse-directive boilerplate constant, currently around line 251) tells the agent:

"Stop key: ship ONE material artifact — a PR opened, a formal review submitted, or an own-PR RC-response cycle — since your last accepted stop…"

But ai/scripts/lifecycle/materialArtifactKey.mjs freezes MATERIAL_ARTIFACT_CLASSES = ['pr-opened', 'formal-review'] and its header comment (line 16) explicitly records: "The own-PR RC-response cycle is deliberately NOT a v1 class: a trustworthy recognizer must bind…". The deferral is a sound engineering decision. The boilerplate over-promising it is not: an agent that ships exactly that artifact class reads the refusal text, believes the stop key is satisfied, emits a valid terminal in good faith, and is refused with no visible reason — the mismatch is invisible from the refusal surface. The same surface also never mentions the compound-command rule, so the pr-opened class can silently fail to mint for an agent that habitually pipes command output (cause 1 above), again with no way to see why from the text the agent is given.

The Architectural Reality

  • .claude/hooks/laneStateStopHook.mjs:251 — the boilerplate string (single real fix site; the .claude/worktrees/** hit is a checkout mirror, and the .codex stop hook does not carry the phrase).
  • ai/scripts/lifecycle/materialArtifactKey.mjs:16,34,42,52 — the deliberate v1 class freeze, the arming anchor (PR_CREATE_HEAD_RE), and the fail-closed compound rule (SHELL_COMPOUND_RE). These are correct and out of scope.
  • The hook is agent-consumed governance substrate: its refusal text is the ONLY feedback surface an agent has at the moment of refusal, so prose/implementation drift here converts directly into wasted correction cycles (two in the specimen session).

The Fix

Align the boilerplate with the implemented v1 contract — string-only, no behavior change:

  1. Replace the Stop-key sentence's class list with the two implemented classes, e.g. "a PR opened (a standalone gh pr create — compound/piped commands never mint the key) or a formal review submitted (manage_pr_review create)". Drop "own-PR RC-response cycle" until the v2 recognizer ships (or annotate it "(planned, not yet a key)").
  2. Keep the sentence's transcript-verified framing intact; do not restate collector mechanics beyond the one-clause standalone-invocation hint (the module's own comments remain the mechanics SSOT).

Acceptance Criteria

  • The refuse-boilerplate's Stop-key sentence lists exactly the classes MATERIAL_ARTIFACT_CLASSES implements, with the standalone-invocation hint for pr-opened.
  • No mention of an RC-response stop key remains on the refusal surface (or it is explicitly marked as not-yet-implemented).
  • No behavior change: collector, evaluator, and hook logic untouched; existing hook/materialArtifactKey unit tests pass unchanged.
  • The worktree mirror is not separately patched (it follows its checkout).

Out of Scope

  • Implementing the v2 RC-response recognizer (the module's documented deferral stands on its own reasoning).
  • Any change to SHELL_COMPOUND_RE / fail-closed arming semantics.
  • The .codex stop-hook text (does not carry the drifted phrase).

Related

  • ai/scripts/lifecycle/materialArtifactKey.mjs (v1 contract + deferral note) · .claude/hooks/laneStateStopHook.mjs (fix site) · #12633 (stop-hook lane-state convergence lineage).
  • Filed per the hook's own friction→gold clause ("if it fired wrong — open a ticket to sharpen it rather than silently absorbing it").

Live latest-open sweep: checked latest 20 open issues at 2026-07-24T08:19Z; no equivalent found. A2A claim sweep (all-status, last 15, ~80-min window): no overlapping [lane-claim]/[lane-intent].

Structure-map gate: N/A — no new or relocated file; the fix edits an existing string constant in the established .claude/hooks/ home.

Decision Record impact: none.

Release classification: not release-blocking (agent-harness substrate; boardless).

Origin Session ID: 2cca0fff-6354-4036-bfdd-fc3320938015

Retrieval Hint: query_raw_memories("stop-hook material artifact key compound command RC-response boilerplate drift")