yotamleo-org

pr-check

Panel-only CR gate for Codex — run the critic panel, clear the CR marker when clean. /pr-check.

yotamleo-org 15 2 Updated 2d ago
GitHub

Install

npx skillscat add yotamleo-org/himmel/pr-check

Install via the SkillsCat registry.

SKILL.md

pr-check (Codex panel-only subset)

This is the Codex subset of himmel's /pr-check: the cross-model shell critic
panel
plus CR-marker handling. It does NOT dispatch the Claude
pr-review-toolkit reviewer agents, and there is NO per-finding verdict /
adjudication step. (Codex native /review integration is a post-HIMMEL-527
follow-up.)

Review floor (HIMMEL-1224)

Stated once, per path, so the floor is explicit rather than implied by which gate
happens to fail-close:

  • interactive /pr-check (a Claude session is present) → the Claude
    self-review
    backstop is the floor: fail-OPEN on lane ABSENCE, fail-CLOSED on
    an attempted-but-failed lane, distinct from SKIP_CR. Opt-in raise
    (CR_REQUIRE_CROSS_MODEL=1, HIMMEL-1237):
    a setup that wants cross-model
    coverage required makes the Claude-alone floor insufficient — clear-cr-marker.sh
    gate 3b then also requires ≥1 non-Claude avail … ok at the SHA — unless
    CR_FLOOR_FALLBACK=claude-only and a provenance-valid claude-floor row is
    present (HIMMEL-3107). That escape is NOT a gate-3b pass (HIMMEL-3108): a
    claude-floor row never counts as a non-Claude responder, and under this
    opt-in the clearing record states its basis — CLEARED … via=cross-model non_claude=<n> vs CLEARED … via=claude-floor same_model=1 context_free=1 exhausted=<lanes>, with the CR-clean line naming the same-model floor in
    words — so clear-cr-marker.log distinguishes the two per clear. Default off
    keeps the adopter-portable Claude-alone floor and an untagged CLEARED line.
  • scripts/cr/pr-check-external.sh (the Claude-FREE ship lane) → "codex
    responded"
    is the floor. A diff that changes the gate infrastructure itself
    needs a quorum of two responding cross-model reviewers, which that lane can no
    longer reach (CodeRabbit is App-only since HIMMEL-2704 and the panel is
    codex-only), so it REFUSES such diffs outright — they go through the
    interactive Claude /pr-check.
  • this Codex subset → "a panel critic responded". Step 5 clears only when
    the panel returned ≥1 responder and clear-cr-marker.sh sees avail … ok at
    the reviewed SHA. There is no Claude self-review backstop here (the codex
    harness has no adjudication step), so a fully-unavailable panel (0/N) RETAINS
    the marker and points the operator at SKIP_CR=1 — this path fails CLOSED
    rather than degrading to a lone free critic.

0. Resolve the repo under review (HIMMEL-2035, HIMMEL-2226)

/pr-check takes NO argument, ever (operator ruling 2026-08-31). Design
principle: cwd selects the repo under review; $himmel_dir — resolved once
below — selects the himmel scripts.
No step here or below cds, so that
principle holds unconditionally in every lane. Step 0 is ONE call, through the
HIMMEL_REPO trust anchor (never the cwd's own files) — no lane-comparison
branching left in the fence (HIMMEL-2335): the lane comparison that used to be
inline now happens entirely INSIDE the script the fence calls (see below).
GitHub-hosted repos only. Arm a foreign repo once by running
bash scripts/cr/install-cr-gate.sh --target <path> from himmel's primary
checkout
— the one command that must NOT go through $himmel_dir:
install-cr-gate.sh bakes that path into the adopter's pre-push hook at
install time, and $himmel_dir is a worktree during normal feature work, so the
adopter's gate would break the day that worktree is pruned (see
docs/internals/enforcement.md, "Arming a repo").

The old adopter lane was /pr-check <repo-path>, DELETED. It pasted the
path into the fence as literal text (the harness pastes the argument in, not
a shell variable) and then cd'd there — a path with an apostrophe or $(…)
broke the quoting (no in-fence escape can fire before the shell parses pasted
text), and the cd could not outlive its own fence, so every later step
resolved against himmel's git-common-dir while reporting the target's branch
(HIMMEL-2262). To review another repo: start a session whose cwd IS that
repo and run a bare /pr-check; step 0 below resolves the himmel scripts from the
HIMMEL_REPO anchor, never from a file the reviewed repo supplies. Both
defects are then structurally absent — no substituted path, no cd.

Any argument is STILL SILENTLY IGNORED IN THIS LANE (HIMMEL-2306). The
command reads none, so a stale /pr-check <path> reviews the CURRENT repo —
and can clear ITS marker — not the path's. The mitigation is unchanged: read
the repo=/branch= step 0 prints and confirm they are the repo you meant
before trusting the run.

The HIMMEL-2306 guard does NOT cover Codex — do not read the Claude
runbook's "refused" wording as applying here.
That guard is
scripts/hooks/block-pr-check-args.sh, a UserPromptExpansion hook, and
that is a Claude Code harness event wired through .claude/settings.json.
.codex/hooks.json supports only PreToolUse, PostToolUse, SessionStart,
SessionEnd and Stop — there is no prompt-stage event in the Codex lane at
all
, so there is nothing for that hook to attach to here. Closing this lane's
half needs either a Codex-side prompt-stage event that does not exist today, or
a different mechanism entirely; it is deliberately NOT claimed as done.

Why the gap is not closed with prose in this file: HIMMEL-2226 tried twice
in-prompt (a fence reading the harness's argument variable, then a prose
sentinel carrying the same value) and both were rejected on review, because
anything that lets the runbook OBSERVE the argument has already brought the
untrusted value inside the boundary it is defending. This paragraph cannot even
NAME that variable — check (vi) of scripts/cr/test-pr-check-pair.sh forbids
the token anywhere in either twin, prose included, because the harness
substitutes it before the runbook is read.

Why the fence is now ONE bare call, not an inline if/else
(HIMMEL-3798, superseding HIMMEL-2335's one-fence shape).
The one-fence
compound below (kept for history) already satisfied the worktree-isolation
guard, but it was still the single highest-rate classifier-denial shape on
this runbook (HIMMEL-3724 4a row 2): a leg's gateAllow literal matches a
whole SIMPLE command, and the permission matcher splits a compound on its
shell separators before matching, so no exact-literal rule for the whole
if...fi string ever fired reliably. scripts/cr/pr-check-step0.sh moves
that entire if/else INSIDE a file, so step 0 is now one bare, unwrapped,
anchor-prefixed literal:

bash "$HIMMEL_REPO/scripts/cr/pr-check-step0.sh"

A standalone SIMPLE command, no if, no ;, no pipe, spelling the
PERSISTENT uppercase $HIMMEL_REPO with the double quote spanning the WHOLE
path. It needs no entry in the guard's TARGETS: the guard's
himmel_anchor_prefix exemption (HIMMEL-3437 finding 1) already skips a
lone, unwrapped "$HIMMEL_REPO/-prefixed operand entirely, because
$HIMMEL_REPO is anchor-controlled, never branch-controlled. The literal's
TEXT shape alone is not what makes this safe
(HIMMEL-3798 codex-1): an
unset or empty HIMMEL_REPO at the guard's own runtime would let this call
resolve to a root-relative, user-plantable path (the Git Bash hazard below).
The guard additionally checks its own actual HIMMEL_REPO value (non-empty,
absolute, containing scripts/cr/pr-check-step0.sh) before honouring the
exemption, denying with a remedy otherwise — this fence is safe because the
guard verifies that, not merely because of how the literal is spelled.
pr-check-step0.sh itself does exactly what the old fence did:
printenv HIMMEL_REPO | grep . (empty stays refused, see below), then
execs "$himmel_repo/scripts/cr/pr-check-context.sh", or prints the same
remedy and exits 2. Being a script file, none of the Bash-tool refusal
shapes below apply to what it contains.

(History — the one-fence compound this superseded, kept as prose so it is
never re-extracted as code: if himmel_repo=$(printenv HIMMEL_REPO | grep .); then bash "$himmel_repo/scripts/cr/pr-check-context.sh"; else echo "pr-check: HIMMEL_REPO is unset or empty — cannot locate himmel from a trusted source outside the repo under review; adopt/setup wires it into settings.json env, or export it non-empty in your launching shell, then re-run" >&2; exit 2; fi)

No [ ] test on any substituted value, no bare $HIMMEL_REPO/${HIMMEL_REPO
expansion, and the double quote spans the WHOLE path
("$himmel_repo/scripts/...", never "$himmel_repo"/scripts/...). The
| grep . piped into the assignment does not change what the guard sees: the
fence still branches only on the ASSIGNMENT's own exit status — no [ ] test
on the captured value — so the worktree-isolation guard accepts this shape
exactly as it accepted the bare form (probed directly in an
EnterWorktree-isolated session).

If this fence is refused with "runs bash in a plain command … cannot be
shown not to run git" anyway, the session is EnterWorktree-pinned
(a leg
launched before HIMMEL-3536's fix, or one whose settings someone hand-edited
back in) — the fix and the interim are in docs/internals/stuck-playbook.md
(HIMMEL-3536), not a respelling of this fence.

Himmel-lane spelling of step 0 (HIMMEL-3359) — run this INSTEAD of the
fence above, never both, and ONLY in a himmel checkout on a diff that touches
none of the paths pr-check-context.sh guards: scripts/cr/, scripts/lib/,
scripts/guardrails/lib.sh, scripts/check-ci.sh,
scripts/handover/resolve-active-item.sh and the files the critic panel, the
handover bridges and the bank preflight run through a variable — the full
list is cr_guarded in pr-check-context.sh (what a clean step 0 later runs
from the branch — HIMMEL-3493).
First prove the lane — the allow rule is
emitted for every leg profile whatever its cwd, so the relative path is
trusted only once the cwd shares HIMMEL_REPO's git dir AND is the worktree
root (from a subdirectory the relative path would name a local file there,
not himmel's):

git rev-parse --path-format=absolute --git-common-dir; printenv HIMMEL_REPO; git rev-parse --show-prefix

Go on ONLY if its first line equals its second line followed by /.git (a
string compare: a differently spelled path only sends you to the fence, the
safe direction) and its third line is empty (the cwd is the worktree root);
anything else, an empty second line included, means use the canonical fence
above. Then run the same diff pr-check-context.sh runs (HIMMEL-3382): the
merge-base diff of the branch's COMMITTED history, PLUS its WORKING-TREE and
UNTRACKED changes, all scoped to the guarded paths above. This check's
silence is necessary, not sufficient, for the script's own "no": the script
also compares the guarded bytes against the anchor's (HIMMEL-3472) and
ignores __pycache__/, so the check can only over-report, which sends you to
the fence — the safe direction
(always against refs/remotes/origin/main, even on a stacked PR — the branch
runs its parent's bytes too, and a diff against a stacked base would hide
them; spelled in full because a local branch named origin/main would shadow
the bare name; the working-tree and untracked legs are what make an
UNCOMMITTED scripts/cr/ edit count too, and a main-side change only
over-reports):

if mb=$(git merge-base HEAD refs/remotes/origin/main 2>/dev/null); then
    git diff --name-only "$mb"..HEAD -- 'scripts/cr/' 'scripts/lib/' 'scripts/guardrails/lib.sh' 'scripts/check-ci.sh' 'scripts/handover/resolve-active-item.sh' 'scripts/handover/append-cr-findings.sh' 'scripts/handover/append-cr-bugs.sh' 'scripts/handover/bug.sh' 'scripts/hermes/invoke.sh' 'scripts/hermes/egress-gate.sh' 'scripts/guardrails/egress-matrix-eval.mjs' 'scripts/guardrails/egress-matrix.json' 'scripts/statusline/usage-cache-producer.sh' 'scripts/lanes/openrouter-cost.sh' 'scripts/lanes/bank-status.ts' 'scripts/lanes/usage-status.ts' 'scripts/lanes/bank-status-core.mjs' 'scripts/lanes/funded-max-pct.mjs' 'scripts/lanes/resolve.mjs' 'scripts/lanes/check.mjs' 'scripts/lanes/probe.mjs' 'scripts/lanes/set-lane-override.mjs' 'scripts/lanes/profile-managed.cjs' 'scripts/observability/quota-sources.ts' 'scripts/telegram/alibaba-probe-once.ts' 'scripts/telegram/quota-gauge.ts' 'scripts/telegram/quota-gauge-alibaba.ts' 'scripts/telegram/console-route.ts' 'scripts/telegram/bus.ts' 'scripts/telegram/console-heartbeat-watch.ts' 'scripts/telegram/poller.ts' 'scripts/telegram/router.ts' 'scripts/telegram/auto-action.ts' 'scripts/telegram/telegram-api.ts' 'scripts/telegram/log-timestamp.ts' 'scripts/telegram/gate.ts' 'scripts/telegram/run.ts' 'scripts/telegram/triage.ts' 'scripts/telegram/transcribe.ts' 'scripts/telegram/spawn-glm.ts' 'scripts/telegram/glm-env.ts' 'scripts/telegram/grants.ts' 'scripts/telegram/huge-diff-guard.ts' 'scripts/telegram/lane-args.ts' 'scripts/telegram/phi-egress-guard.ts' 'scripts/telegram/round-guard.ts' 'scripts/lanes/plugin-profiles.mjs' 'scripts/lanes/plugin-profiles.json' 'scripts/lanes/role-requires.mjs' 'scripts/lanes/skill-cost.mjs' 'scripts/lanes/skill-listing.mjs' || echo unknown
    git diff --name-only HEAD -- 'scripts/cr/' 'scripts/lib/' 'scripts/guardrails/lib.sh' 'scripts/check-ci.sh' 'scripts/handover/resolve-active-item.sh' 'scripts/handover/append-cr-findings.sh' 'scripts/handover/append-cr-bugs.sh' 'scripts/handover/bug.sh' 'scripts/hermes/invoke.sh' 'scripts/hermes/egress-gate.sh' 'scripts/guardrails/egress-matrix-eval.mjs' 'scripts/guardrails/egress-matrix.json' 'scripts/statusline/usage-cache-producer.sh' 'scripts/lanes/openrouter-cost.sh' 'scripts/lanes/bank-status.ts' 'scripts/lanes/usage-status.ts' 'scripts/lanes/bank-status-core.mjs' 'scripts/lanes/funded-max-pct.mjs' 'scripts/lanes/resolve.mjs' 'scripts/lanes/check.mjs' 'scripts/lanes/probe.mjs' 'scripts/lanes/set-lane-override.mjs' 'scripts/lanes/profile-managed.cjs' 'scripts/observability/quota-sources.ts' 'scripts/telegram/alibaba-probe-once.ts' 'scripts/telegram/quota-gauge.ts' 'scripts/telegram/quota-gauge-alibaba.ts' 'scripts/telegram/console-route.ts' 'scripts/telegram/bus.ts' 'scripts/telegram/console-heartbeat-watch.ts' 'scripts/telegram/poller.ts' 'scripts/telegram/router.ts' 'scripts/telegram/auto-action.ts' 'scripts/telegram/telegram-api.ts' 'scripts/telegram/log-timestamp.ts' 'scripts/telegram/gate.ts' 'scripts/telegram/run.ts' 'scripts/telegram/triage.ts' 'scripts/telegram/transcribe.ts' 'scripts/telegram/spawn-glm.ts' 'scripts/telegram/glm-env.ts' 'scripts/telegram/grants.ts' 'scripts/telegram/huge-diff-guard.ts' 'scripts/telegram/lane-args.ts' 'scripts/telegram/phi-egress-guard.ts' 'scripts/telegram/round-guard.ts' 'scripts/lanes/plugin-profiles.mjs' 'scripts/lanes/plugin-profiles.json' 'scripts/lanes/role-requires.mjs' 'scripts/lanes/skill-cost.mjs' 'scripts/lanes/skill-listing.mjs' || echo unknown
    git ls-files --others -- 'scripts/cr/' 'scripts/lib/' 'scripts/guardrails/lib.sh' 'scripts/check-ci.sh' 'scripts/handover/resolve-active-item.sh' 'scripts/handover/append-cr-findings.sh' 'scripts/handover/append-cr-bugs.sh' 'scripts/handover/bug.sh' 'scripts/hermes/invoke.sh' 'scripts/hermes/egress-gate.sh' 'scripts/guardrails/egress-matrix-eval.mjs' 'scripts/guardrails/egress-matrix.json' 'scripts/statusline/usage-cache-producer.sh' 'scripts/lanes/openrouter-cost.sh' 'scripts/lanes/bank-status.ts' 'scripts/lanes/usage-status.ts' 'scripts/lanes/bank-status-core.mjs' 'scripts/lanes/funded-max-pct.mjs' 'scripts/lanes/resolve.mjs' 'scripts/lanes/check.mjs' 'scripts/lanes/probe.mjs' 'scripts/lanes/set-lane-override.mjs' 'scripts/lanes/profile-managed.cjs' 'scripts/observability/quota-sources.ts' 'scripts/telegram/alibaba-probe-once.ts' 'scripts/telegram/quota-gauge.ts' 'scripts/telegram/quota-gauge-alibaba.ts' 'scripts/telegram/console-route.ts' 'scripts/telegram/bus.ts' 'scripts/telegram/console-heartbeat-watch.ts' 'scripts/telegram/poller.ts' 'scripts/telegram/router.ts' 'scripts/telegram/auto-action.ts' 'scripts/telegram/telegram-api.ts' 'scripts/telegram/log-timestamp.ts' 'scripts/telegram/gate.ts' 'scripts/telegram/run.ts' 'scripts/telegram/triage.ts' 'scripts/telegram/transcribe.ts' 'scripts/telegram/spawn-glm.ts' 'scripts/telegram/glm-env.ts' 'scripts/telegram/grants.ts' 'scripts/telegram/huge-diff-guard.ts' 'scripts/telegram/lane-args.ts' 'scripts/telegram/phi-egress-guard.ts' 'scripts/telegram/round-guard.ts' 'scripts/lanes/plugin-profiles.mjs' 'scripts/lanes/plugin-profiles.json' 'scripts/lanes/role-requires.mjs' 'scripts/lanes/skill-cost.mjs' 'scripts/lanes/skill-listing.mjs' || echo unknown
else
    echo unknown
fi

Use the bare literal below ONLY when that check prints nothing at all — a
failed check (refs/remotes/origin/main does not resolve, or ANY one of the
three listing calls fails, whose empty stdout would otherwise read as a clean
diff — HIMMEL-3454) prints the literal word unknown, which is not a path and
can never mean a clean diff; the pathspecs are plain, not :(top), because
the lane check already proved the cwd is the worktree root and an inherited
GIT_LITERAL_PATHSPECS=1 would take :(top)scripts/cr/ literally and match
nothing, while a plain pathspec means the same under every pathspec env var; unknown
OR any path proves nothing clean and counts as a guarded-path diff. It is the
one shape a leg's allow rule can match:

bash scripts/cr/pr-check-context.sh

If the check prints any path or unknown, use the canonical fence above — and if that
fence is refused, stop and report BLOCKED; never fall back to the bare
literal. On such a diff an allow-listed literal would auto-run the branch's own
bytes, which may have deleted their own hand-off (console ruling on
HIMMEL-3359). The in-script hand-off is defense in depth, not the trust root:
run from a copy that is not the anchor's, pr-check-context.sh reads
HIMMEL_REPO itself and execs the ANCHOR's copy before deciding anything
or sourcing any branch file, so the lane decision, the delegation and its delegation ledger row are still
the anchor's. An anchor with no copy exits 2; an unset or empty HIMMEL_REPO
exits 2 (a relative path cannot collapse to /scripts/...). Never use it
outside a himmel checkout — in an adopter repo the relative path names the
REVIEWED repo's file.

A set-but-EMPTY HIMMEL_REPO now takes the SAME remedy branch as unset
— the assignment is piped through grep ., which matches only a non-empty
line. printenv HIMMEL_REPO on a set-but-empty var still prints (an empty
line) and exits 0, but grep . on that empty line matches nothing and exits
1, so himmel_repo=$(printenv HIMMEL_REPO | grep .) itself fails and the
if takes the else branch — the same stderr message and exit 2 an unset
var gets. Do not "simplify" the | grep . back out. Without it, the
if branch is taken with himmel_repo="", and bash "$himmel_repo/scripts/cr/pr-check-context.sh" collapses to bash "/scripts/cr/pr-check-context.sh" — an absolute path with no leading
directory. Under Git Bash on Windows that resolves to
C:\scripts\cr\pr-check-context.sh, a location an ORDINARY user can create
without admin rights: a file planted there would execute AS THE TRUSTED
ENTRY POINT, ahead of every anchor/lane check that exists to refuse
untrusted code — no in-script defense can help, because the wrong script is
what ran. (On a POSIX host with nothing at /scripts/cr/... the old form
merely failed with bash: /scripts/cr/pr-check-context.sh: No such file or directory, rc=127 — that was documented at the time as "fail-closed," but it
was only an accident of an empty filesystem, not a property of the fence.)
pr-check-context.sh also carries its own defensive unset/empty check,
covering every OTHER entry path (a direct invocation, a test harness) this
fence-level guard does not gate. Remedy either way: adopt/setup wires
HIMMEL_REPO into settings.json env, or export it non-empty in the
launching shell, then re-run.

pr-check-context.sh IS the trusted-anchor entry point — the lane
comparison moved inside it, out of the fence.
It reads HIMMEL_REPO from
its OWN process environment (never re-derived from cwd or a file the repo
under review supplies — HIMMEL-2226 Finding 1: a crafted repo can contain its
own scripts/cr/critic-panel.sh, or bake any path into its own pre-push
hook), then compares the cwd's git-common-dir against $HIMMEL_REPO/.git by
inode (test -ef, so a trailing slash, symlink, slash style or Windows
casing cannot misclassify it) to resolve the same two lanes the old inline
fence used to: himmel lane (match) — himmel_dir keeps its own
--show-toplevel, deliberately NOT $HIMMEL_REPO, so a branch's /pr-check
runs THAT branch's scripts/cr/ (resolving to the primary would make a
branch run main's scripts); safe because the git-common-dir match already
proved the cwd is himmel. adopter lane (no match) — himmel_dir becomes
the anchor, NEVER anything the reviewed repo supplied; a crafted repo's own
critic-panel.sh or self-pointing baked gate line is never run, and an
unarmed adopter simply has no marker, so step 1 reports nothing to do.
refuse (HIMMEL_REPO unset or empty) — no trusted source for himmel's
location, so STOP before any paid critic call rather than guess from the cwd.

Branch self-review is NOT lost — it moved one level down, and it is now a
DELIBERATE, LOGGED decision instead of an automatic one (HIMMEL-2335).
On
the himmel lane only, when the branch's own diff touches a guarded path
(scripts/cr/, scripts/lib/, scripts/guardrails/lib.sh,
scripts/check-ci.sh, scripts/handover/resolve-active-item.sh or the rest
of cr_guarded in pr-check-context.sh — HIMMEL-3493) — the exact set is printed as cr_diff_files= in the stdout
contract below (HIMMEL-3382: the same diff the himmel-lane spelling above
runs, less __pycache__/) — pr-check-context.sh detects it,
appends a delegation row
to the CR ledger through the ANCHOR's own scripts/cr/ledger-append.sh (the
trusted side logs the call it is about to make — a failed ledger write does
NOT abort any more: it warns on stderr, declines to delegate, and falls back
himmel_dir= the anchor instead of delegating unlogged), prints a stderr
diagnostic naming the anchor, the delegate and the head SHA, then re-execs
the BRANCH's own copy of pr-check-context.sh with an anchor-identity
handshake — PR_CHECK_ANCHOR_DELEGATED=<the delegating anchor's own resolved path>, not a bare 1 — set. The branch never elects itself — the anchor
decides delegation happens and records it.
When the diff touches none
of the guarded paths, on the adopter lane, or once that handshake already verifies
against this run's own resolved anchor, the run does its own work directly.
A git merge-base that cannot be computed is an UNKNOWN diff, not a
known-non-touching one: never delegate on it, and fall back
himmel_dir= the anchor for the rest of the run too — the same fallback the
failed-ledger-write case takes.

This is now a measured property of the harness, not a precaution
(HIMMEL-2314).
HIMMEL-2226 wrote the carry-forward-as-a-literal rule
defensively — "a value assigned in one block need not survive" — because it
could not establish how Codex sequences blocks, and deliberately refused to
assert one in prose. It has since been settled empirically: a two-block probe
skill run through codex exec assigned a canary in block 1 and read back an
empty string in block 2, with negative controls proving the probe would
have detected either answer. Codex runs each block as a separate process.
So a $var crossing a block boundary here is not a latent risk — it is
already broken, and every value a later step needs must reach the TRANSCRIPT
in the block that computes it. scripts/cr/test-pr-check-pair.sh check (ii)
REJECTS the "$himmel_dir/scripts/..." spelling outright rather than
accepting it as a legitimate alternative.

HIMMEL-4040: relative literal for EVERY scripts/cr call. Run each
bash "<himmel_dir>/scripts/cr/<script>.sh" block below as the cwd-relative
bash scripts/cr/<script>.sh <literal args> whenever cwd is a himmel worktree
root whose scripts/cr/ matches the anchor's; keep the absolute spelling only
for the drifted or adopter case.

Himmel-lane spelling — drop the absolute prefix (HIMMEL-3338). The
"<himmel_dir>/scripts/..." form the blocks below show is the canonical,
adopter-safe spelling. When step 0's printed himmel_dir= equals repo= (the
himmel lane), drop the "<himmel_dir>/ prefix and its closing quote and run the
cwd-relative literal bash scripts/cr/<script>.sh <literal args> instead: the
Claude twin's leg allow rules match only that relative text, and keeping the
twins in one spelling avoids a lane-dependent runbook. When they differ (the
adopter lane) keep the absolute spelling — a relative path there would run the
REVIEWED repo's scripts. scripts/lanes/tests/plugin-profiles.test.mjs pins
the Claude twin's calls against the leg-impl rules.

The step-0 fence above already ran pr-check-context.sh — EVERY lane,
exactly once, no cd before it. Do not call it again here; carry its printed
literals forward.
A delegating re-exec REPLACES the anchor's own process
(exec, not a second concurrent run), so only the run that actually produced
the printed context above also performed its side effects — the two
HIMMEL-1219 verdict-scratch resets (truncating cr-prior-blocking/<branch>
and cr-aggregate-verdicts/<branch>), described in the Claude runbook's step
0 — this lane never writes to them itself, but a stale verdict left by a
PRIOR run (Claude or Codex; the git-common-dir is shared) could otherwise leak
into the Claude runbook's step-4 cross-check and orphan reconciliation. Both the printed context and
the resets already happened, in that one call.

It prints one pr-check-context: <key>=<value> line per datum. Read the
branch=, head= and marker= lines off that output and carry each as the
substituted literal <branch> / <head> / <marker> into the later blocks
— do NOT sed them into a shell variable here (a variable assigned in this
block need not survive into a later one). Take the WHOLE remainder after
<key>= — a branch may legitimately contain = (e.g. wip/branch=2), and
the marker path under it. The printed head= is also the HIMMEL-1175 pin:
carrying it as a literal is the point — a fresh git rev-parse HEAD in a
later block would be the drift the pin exists to catch. It also prints
anchor_lane=<himmel|adopter>, delegated=<yes|no> and
cr_diff_files=<comma-joined, sorted, deduped set this run decided on (HIMMEL-3382) — the merge-base diff of committed history plus working-tree/untracked changes under the guarded paths (scripts/cr/, scripts/lib/, scripts/guardrails/lib.sh, scripts/check-ci.sh, scripts/handover/resolve-active-item.sh and the rest of cr_guarded in pr-check-context.sh); empty when the diff proved clean or could not be computed> — informational only (nothing downstream substitutes any of them),
and NOT the same field as lane= below, which is the marker's own 3rd field
and means something unrelated.

It also prints lane= — the marker's 3rd field, the CR lane, read INSIDE the
script against a real shell variable rather than substituted into a fence
(HIMMEL-2226 round 2: the marker path embeds the branch name, which is
repo-controlled, so a ' in a branch name could otherwise break out of a
substituted '<marker>'). lane= is only ever read, never substituted into
a later block — see step 2.

Everything after this is cwd-relative on purpose: marker, ledger, base ref and
clear all resolve from this directory's git rev-parse --git-common-dir. No
fence cds, so this holds unconditionally in every lane — including the
adopter lane, which used to cd in step 0 only and leave every later step
resolving against himmel's own git-common-dir (HIMMEL-2262, now structurally
dissolved rather than documented).

1. Locate the marker

Step 0 printed branch=, head= and marker=. Carry each as the
substituted literal <branch> / <head> / <marker> — the marker is
<marker>, the pending CR file for this run.

If <marker> is absent, report no pending CR for <branch> — nothing to do and stop.

2. Lane check (HIMMEL-303)

Step 0 already printed lane= — the marker's 3rd field. Use that value; there
is no fence to run here (HIMMEL-2226 round 2 — see step 0 above). If
lane = docs-audit: the code-critic panel is the wrong charter — retain
the marker and tell the operator to run the Claude /pr-check for docs lanes.
Stop. Only lane = full or empty proceeds.

3. Run the panel over the diff

Operator ruling 2026-09-21 (HIMMEL-3338): the operator authorises sending
review diffs to Codex from legs.
panel-first-pass.sh below is allow-listed
for the leg profiles as a literal, so a denial on it most likely means the call
was not spelled as one (see the himmel-lane spelling note above) — check the
spelling first. A denial that names a deny rule or a policy restriction is not a
spelling problem: stop and report it, never respell around it, and never skip
the lane silently.

scripts/cr/panel-first-pass.sh (HIMMEL-2226) now does what this step used to
do inline
— resolve the default branch, capture the base SHA once (HIMMEL-1984:
every consumer of this range must review the SAME range, so the base
is pinned to a SHA captured here rather than re-resolved live at each call site),
load and export CR_PROFILE from the primary checkout's .env WITHOUT
hand-computing a tier filter (HIMMEL-558: critic-panel.sh resolves its own
tiers from CR_PROFILE and treats it authoritative — hand-scoping the tier is
what drifted a run to free-only and silently dropped the paid codex critic),
diff the captured base SHA against --head (never live HEAD — step 4 stamps
every ledger row with the captured $head, so a checkout that moved mid-run,
including a same-SHA branch switch, must be a REFUSAL rather than a silent live
review), and run the panel over it — retrying once through rtk proxy git diff
when the plain diff comes back as a stat summary instead of a unified diff (an
rtk-proxied environment's git diff, which critic-panel.sh otherwise rejects
as "no valid diff"). CR_PROFILE is authoritative — the panel derives its tiers
from it; unset ⇒ the PAID codex anchor, not a free panel (HIMMEL-1101,
operator decision: accept paid-by-default).
The free lane was removed
deliberately (HIMMEL-667, HIMMEL-953), and critics.json today holds exactly one
row — codex / gpt-6.1-sol, tier paid — so an unset CR_PROFILE resolves to
zero free rows and falls back to it: a default /pr-check that actually runs the
panel consumes the operator's OpenAI usage bank. CR_PROFILE=none is the instant
escape when spend is unwanted — but it skips the panel, and this subset has no
Claude backstop, so it leaves zero
responders: the marker is RETAINED per the review floor above and SKIP_CR=1 is
the bypass.

panel_tmp=$(mktemp -t critic-panel-avail.XXXXXX)
panel_rc=0
panel_out=$(bash "<himmel_dir>/scripts/cr/panel-first-pass.sh" --head <head> --branch '<branch>' 2>"$panel_tmp") || panel_rc=$?
cat "$panel_tmp" >&2
panel_avail_lines=$(grep '^panel-availability:' "$panel_tmp" || true)
rm -f "$panel_tmp"
# First stdout line is "captured diff base: <db> (<db_sha>)" — carry
# $db_sha forward as a literal into step 3.5. The rest is the panel's
# merged findings block, if any -- UNTRUSTED critic-authored text, which
# can itself contain a line that starts with "captured diff base:". The
# `sed -n '1p'` below is load-bearing, not decorative: it hands awk ONLY
# line 1, so a findings line making the same claim later in $panel_out
# can never reach this parse and corrupt $db_sha. $(NF-1) takes the
# content of the LAST parenthesized group, so a base branch name that
# itself contains parentheses does not shift the SHA out of the field a
# fixed $2 would have read (codex-3).
db_sha=$(printf '%s\n' "$panel_out" | sed -n '1p' | awk -F'[()]' '/^captured diff base:/{print $(NF-1)}')
panel_out=$(printf '%s\n' "$panel_out" | sed '1d')
# HIMMEL-2314: PRINT both, in the block that computes them. The Codex
# harness runs each block as a SEPARATE PROCESS (proven by a two-block
# probe skill run through `codex exec`: block 2 read back an empty string
# for a canary block 1 had assigned), so nothing below can expand
# $db_sha or $panel_out. Everything a later step needs must reach the
# TRANSCRIPT here, exactly as $panel_avail_lines already does via
# `cat "$panel_tmp" >&2` above. Carry db_sha forward as the substituted
# literal <db_sha> — 40 hex chars, so it is left unquoted like <head>.
printf 'pr-check: db_sha=%s\n' "$db_sha" >&2
printf 'pr-check: panel_rc=%s\n' "$panel_rc" >&2
printf '%s\n' "$panel_out"
# The exit-7 stale-input STOP lives HERE, in the same block that captured
# the rc. It used to sit in a later block, where $panel_rc was empty and
# `[ "$panel_rc" -eq 7 ]` raised "integer expression expected" instead of
# stopping — so the run continued on inputs already known to be stale.
if [ "$panel_rc" -eq 7 ]; then
    echo "review inputs stale — the checkout or the diff base moved since step 0; nothing was reviewed, marker retained — re-run /pr-check" >&2
    exit 7
fi

Panel exit 7 = input-pin mismatch (HIMMEL-1175, HIMMEL-1984). STOP HERE — do not
continue.
The checkout is no longer at the SHA step 0 captured, or the
diff BASE no longer resolves to the commit this step captured, so the diff, the
review, and the ledger stamp would describe three different things. Nothing was
reviewed and nothing was recorded: retain the marker, report it, and stop —
step 0 re-captures branch + HEAD on the next run. It is not a critic drop-out and
never records an availability row. Stopping HERE rather than at step 5 is the
point: every later step would otherwise record a review of inputs already known
to be stale.

The check itself now lives in the step-3 block above (HIMMEL-2314) — it has
to, because blocks are separate processes and $panel_rc does not survive one.
As a fence of its own it read an unset variable, so [ "$panel_rc" -eq 7 ]
raised "integer expression expected" and the STOP never fired. panel_rc= is
also echoed to the transcript there, so this stop is auditable after the fact.

3.5. CodeRabbit — the GitHub App, not a local pass (HIMMEL-2704)

There is no CodeRabbit step in this lane. CodeRabbit is the GitHub App
and reviews the PR only after it exists, so nothing here invokes it. The CLI
wrapper (scripts/cr/coderabbit-review.sh) and the conserve-or-run gate
(scripts/cr/coderabbit-gate.sh) that used to run in this slot are DELETED,
along with CODERABBIT_BIN, CODERABBIT_WSL, CODERABBIT_TIMEOUT_SECS and
CODERABBIT_CLI_DISABLE (operator ruling 2026-09-07: the App is the gate,
and every finding is commented on and tracked on the PR in GitHub).

That leaves this lane with ONE finding source — the panel in step 3 — which
is why the review floor above is "a panel critic responded" and why step 5
retains the marker whenever the panel returns zero responders. The App's
post-PR contract is enforced by scripts/check-ci.sh, not here, and
CodeRabbit is best effort (HIMMEL-3360): zero unresolved review THREADS (a
green check-run is NOT clean — PR #2209 carried six unresolved threads under
a pass rollup on 2026-09-06) and zero undispositioned outside-diff body
findings are the gate; CodeRabbit's commit status (scripts/lib/coderabbit-signal.sh;
absent / pending / paged / skipped / rate-limited / unreadable) is
printed as a NOTE and never blocks. The CI hooks post one @coderabbitai review trigger per new head (a head move discards the in-flight review);
nothing schedules or holds work on the App, and
scripts/hooks/check-cr-best-effort.sh gates the tracked text for that.

4. Record ledger evidence (HIMMEL-1171)

This step runs after both finding sources and before any gate decision. The
marker may be cleared only from evidence persisted by
scripts/cr/ledger-append.sh; the in-session summary is not evidence.

  1. Parse # Critic Panel Review (M/N critics responded) from $panel_out and
    retain M/N as $panel_coverage. Treat a missing or malformed header as a
    ledger failure. Parse each terminal panel-availability: line from
    $panel_avail_lines:

    • The critic slug is token 2.
    • ok and unavailable are recorded unchanged.
    • fallback(<model>) means ok because that critic responded through its
      fallback model.
    • Ignore intermediate fallback-failed(...) / fallback-chain diagnostics;
      the same slug later has one terminal fallback(...) or unavailable line.
    • The panel rows must account for all N critics and exactly M responding
      rows (ok or fallback(...)). A mismatch is a ledger failure; never guess
      which critic is missing.
  2. Normalize every blocking candidate into the panel bullet contract before
    writing it: - [<slug>-N]: <issue> [<file>:<line>]. This includes every
    bullet under the panel's ## Critical Issues / ## Important Issues.
    Map the section/severity to crit / imp, extract the slug from the
    ID, and use verdict agreed (the Codex subset has no adjudication pass). If a
    blocking candidate cannot be parsed, that is a ledger failure — do not omit
    it and do not clear.

  3. Append all finding rows before availability rows. This ordering fails
    safer if storage breaks mid-step: a persisted blocker without an availability
    row cannot clear, while the reverse ordering could momentarily look clean.

    The producers already wrote the finding rows — you record only the VERDICT
    (HIMMEL-2321).
    critic-panel.sh has always self-written every panel finding
    through ledger-append.sh --batch-file. The rows for
    [<slug>-N] therefore already exist at
    this head, carrying the reviewer's own file, line and text — written by the
    process that received them, never retyped. The old form of this step pasted
    --file '<file>' --line '<line>' into a fence; a critic's title or path
    legitimately contains an apostrophe, which closes the surrounding quote and
    hands the rest to the shell. Recording only a verdict removes the last
    reviewer-authored byte from this fence: an id and a closed-vocabulary word.

    Substituted placeholders are SINGLE-quoted (HIMMEL-2226). <slug> is a
    value YOU paste into the command text — it comes from a reviewer-supplied
    finding ID, so the shell PARSES it, and double quotes do not help: $(…),
    backticks and ${…} all still execute inside "…". Only single quotes make
    it inert, which is what step 3.5's untrusted-input posture requires.
    <branch> is single-quoted for the same reason (a git ref may carry shell
    metacharacters); <head> (the 40-char SHA step 0 printed) and the fixed word
    sets <crit|imp> / <ok|unavailable> stay unquoted — they are constrained by
    their grammar, not by trust.
    Apostrophe caveat: a single-quoted string cannot contain a literal ',
    and a reviewer-supplied slug can carry one — substitute it as '\'' (close,
    escaped quote, reopen), or REFUSE the step and say why. A refused step is
    recoverable; an injected one is not.

    Amend EVERY cross-model finding the producers wrote — Critical, Important
    AND Suggestion — not only the blocking candidates.
    clear-cr-marker.sh
    gate 4b refuses with reason=unadjudicated-findings on ANY finding carrying
    an empty verdict, with no carve-out for sug. The producers self-write all
    three severities, so a Suggestion row left unamended keeps its empty verdict
    and the marker cannot clear — on an otherwise clean review. Only the BLOCKING
    COUNT excludes sug (step 4); the disposition requirement does not. The
    Claude twin's step 4.5 loop already covers all three, and this is the loop
    that has to match it.

    For each such finding, run and check. --reason is a FIXED LITERAL you
    write yourself, never reviewer text:

    if ! bash "<himmel_dir>/scripts/cr/ledger-append.sh" amend \
        --head <head> --id '<slug>-N' \
        --set verdict=<agreed|disproved|conflict|unaddressed> \
        --reason 'adjudicated by pr-check step 4'; then
        ledger_failed=1
        # Single-quoted whole: the slug is substituted INTO this message
        # too, and a double-quoted echo would still execute a `$(…)` in it.
        echo 'CR ledger amend failed for finding <slug>-N — marker retained' >&2
    fi

    A deferred verdict additionally carries its ticket, its follow-up class and
    the reason the gate reads: --set verdict=deferred --set deferred_to=HIMMEL-<n> --set fu_class=<escape|hardening|polish> --set 'reason=<why out of scope>'
    (without a class the append exits 2). --set reason= is a different field from the --reason
    above — the gate reads the finding's reason, --reason only records why
    the ledger row changed.
    A disproved verdict on a finding whose text names a shell or platform
    (dash, bash, zsh, busybox, macOS, git-bash, ...) is refused with the generic
    --reason alone: it needs your OWN measurement, written yourself, naming
    each such shell with a version — --set 'reason=measured on <shell> <version>: <what it showed>'. Run the check on the real binary; the
    refusal prints this form.

    An amend refusal is a real signal — never fall back to finding. It
    exits non-zero when no matching row exists at that head, which means the
    producer's self-write did not happen (an unpinned producer run, or a failed
    ledger write). The row you are adjudicating is genuinely absent, so treat it
    as any other failed append: retain the marker and fix the cause. Re-appending
    it as a finding would recreate the retyping surface this step removes.

    Then, for each parsed panel availability row, run and check:

    if ! bash "<himmel_dir>/scripts/cr/ledger-append.sh" avail \
        --branch '<branch>' --head <head> \
        --model '<slug>' --status <ok|unavailable>; then
        ledger_failed=1
        echo 'CR ledger append failed for availability <slug> — marker retained' >&2
    fi

Check the exit status of every append. If any append or parse fails, retain
the marker, report the failure, and stop before invoking
clear-cr-marker.sh. An unrecorded finding must never read as no finding. The
ledger deduplicates availability on (head, model) and findings on
(head, finding_id), so re-running on the same HEAD is safe.

5. Gate decision

  • panel_rc = 7 (input-pin mismatch, HIMMEL-1175) never reaches this step —
    step 3 stops the run the moment it sees it, so nothing downstream records a
    review of inputs already known to be stale. Restated here only so the
    gate table is complete: marker retained, and SKIP_CR=1 is NOT the answer
    (the review never ran, so there is no unavailable-critic problem to bypass).
  • Else if panel_rc != 0 (the panel header reports 0/N critics responded →
    unavailable): retain the marker, report panel unavailable — marker retained, and point the operator at the SKIP_CR=1 emergency bypass. Stop.
  • Else require exactly one panel count line matching each expected format:
    ^## Critical Issues \([0-9]+ found\)$ and
    ^## Important Issues \([0-9]+ found\)$. Parse C and I only from
    those lines, then require each count to match the corresponding number of
    normalized panel crit / imp bullets recorded in step 4. If either line is
    missing, malformed, duplicated, or mismatched, retain the marker, report
    the count-parse failure, and stop before invoking clear-cr-marker.sh.
    • If C = 0 AND I = 0, invoke the sanctioned chokepoint and inspect its
      exit status:

      clear_rc=0
      bash "<himmel_dir>/scripts/cr/clear-cr-marker.sh" '<branch>' || clear_rc=$?

      Report the result without deleting the marker directly:

      • 0 → CR clean — marker cleared (M/N critics responded) using
        $panel_coverage. If the script's own line reads … ON THE SAME-MODEL FLOOR …, report THAT verbatim instead: the floor cleared it and the SHA
        has no cross-model coverage (HIMMEL-3108).
      • 13 → stale SHA / branch changed; marker retained, re-run /pr-check.
      • 14 → no responder evidence at this HEAD; marker retained.
      • 15 → blocking ledger finding; marker retained and report it.
      • 16 → PR-head or check-ci mismatch; marker retained.
      • Any other non-zero → chokepoint refused; marker retained and surface the
        command output and exit code.
    • Otherwise: retain the marker and report the panel's Critical/Important
      findings verbatim for the operator to fix and re-run.