Files
Claude 7ad290e24f fix: three hand-kept copies of a fact that already had one source
Review's second round left five findings. Three are the same defect wearing
different clothes — a fact with ONE authoritative source, restated by hand
somewhere else, where the copy fell behind — so they are closed as classes
rather than patched as places. Two are their own bugs.

DERIVE, DON'T RESTATE

CONFINEMENT. The sweep kept its own copy of the runner's mode expression and
did not know `recordEgress` confines. So a `recordEgress` sweep pre-flighted a
relative script against the host's cwd and env, accepted it, and handed it to a
run that starts in a fresh empty directory with a cleared environment — where
`sh` cannot open the script and exits 2, this harness's DENY code, scored as a
block. `routeScriptRun` is now the one decision: `runScriptWith` dispatches on
it and owns no policy of its own, and the pre-flight asks it. There is no list
here left to fall behind — a future option that selects confinement is added
once, in the runner, and both readers inherit it.

WRAPPERS. `commandFileRefs` read `args[0]` as the head, so `env python3 guard`
named no file at all and the same exit-2 door opened. `stripWrappers` already
owns the wrapper list for the effect classifier, so it is exported and ASKED,
not copied: env / command / timeout / sudo / nice / xargs / nohup, recursively,
skipping each wrapper's own options and `NAME=value` words.

  NOT fixed, and said out loud in the code and pinned by a test so it is
  recorded rather than rediscovered: `exec` is a pass-through that list does
  not carry, and `stripWrappers` has its own recorded remainder (an escaped
  head behind a wrapper, `sudo g\it push`). Both belong in that module, where
  the safety matcher reads the same list and a change deserves the measuring
  a matcher change deserves — not in a private copy here.

EXECUTION CWD. The pre-flight tested files against the CALLER's directory while
the project-root variables named the swept root: two trees, one run. A guard on
disk exactly where its config says was reported `unresolved`; and where the
caller happened to hold the same relative path, the sweep ran and SCORED the
caller's file under the swept repo's name. `effectiveCwd(opts, root)` answers
once, defaults to the swept repository, and the runner is handed that same
answer. An explicit `cwd` still overrides — the installed-plugin case, where
hooks are READ from the plugin and RUN against a project elsewhere.

TWO ORDERING BUGS, THEIR OWN

A read inside an assignment's own initializer is never dominated by that
assignment: the shell expands the RHS and only then binds the name. Keyed on
the assignment's START offset it was, because the walk is preorder — the
`Assign` was recorded before its own `ParamExp` was visited. So
`MODE="$MODE"; [ "$MODE" = strict ]` reported NO dependency, and under
confinement the sweep cleared an ambient value without reporting it unresolved
and scored whichever branch the empty value took. Keyed on the END, the
containment falls out of the arithmetic instead of needing a special case.

`prompt` / `http` / `mcp_tool` / `agent` are real hook actions. `normalizeHooks`
drops them correctly — nothing that spawns a shell can drive one — but the sweep
read the empty list as "No hooks are declared", which is an accusation about the
repository rather than a limit of the tier, written in the one sentence this
report exists to get right. They are carried now as `report.unmeasurable` and
reported declared-and-NOT-measured in the notes, the census and the rendering;
the mixed case is counted too, because a reader who sees six measured hooks
cannot otherwise know a seventh went unexamined.

MUTATIONS

Six, each reverting a fix to its old shape, each failing on its own assertion.
Two passed GREEN first, and both were findings about the tests:

  - `routeScriptRun`'s unit tests pinned the ROUTER, but nothing pinned that
    the SWEEP asks it, so restoring the hand-kept copy went green. Added.
  - the cwd fixture's guard DENIED — and `sh missing.sh` also exits 2, so a
    correct measurement and a total failure to find the file both read 7/7.
    The fixture now ALLOWS, making the right answer 0/7: the one thing the
    false 7/7 cannot be confused with. That is this branch's own bug class,
    found inside its own test.

No published number moves. `WRAPPER_HEADS` is untouched (the bash-effects diff
is a doc comment and an `export` keyword): 7 seeds, 136 alternate spellings and
the 2/7 -> 7/7 dogfood all unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:26:20 +00:00
..