Skip to content

fix: bughunt round 2 — eleven records fixed, iss-201 closed, iss-358 wontfixed on review evidence - #386

Merged
REPPL merged 27 commits into
mainfrom
bughunt-b/round-2
Aug 20, 2026
Merged

fix: bughunt round 2 — eleven records fixed, iss-201 closed, iss-358 wontfixed on review evidence#386
REPPL merged 27 commits into
mainfrom
bughunt-b/round-2

Conversation

@REPPL

@REPPL REPPL commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Autonomous all-dimensions bug-hunt, round 2 (branch bughunt-b/round-2). Baseline (make preflight + gofmt -l .) green before any change and green again on the final merged tree (68 packages tested). Every finding survived an independent adversarial refutation before capture; every behaviour change has a test watched failing first. All findings live in the committed ledger as iss-N records (never as forge issues), captured before the fixes per the record-first rule. Two ids minted this round collided with a parallel hunt's mints that landed on main mid-round; they are re-minted as iss-358/iss-359 per the collision protocol (fresh capture, ids never hand-edited).

Confirmed substantive, fixed

  • iss-359internal/core/cite/fetch.go: a redirect-controlled FinalURL carried C1/bidi/zero-width runes raw into docs cite refresh --json and the committed citations baseline (encoding/json escapes only C0 + U+2028/9 — measured and pinned). The fetch boundary now percent-encodes them byte-wise (lossless on invalid UTF-8 too), Baseline.validate refuses them from any producer, and the false JSON-needs-no-sanitising claims in termsafe.go and lifeboat/coverage.go are corrected. Tests: TestRedirectTargetControlRunesNotRecordedRaw, TestEncodeHiddenRunesKeepsInvalidBytes, TestBaselineRefusesControlRunesInFinalURL, TestJSONLeavesC1AndBidiRaw.
  • iss-346internal/surface/cli/guard.go: the "also matched" line rendered Matches[1:], but Matches is blockers-warns-synthetics ordered, so a synthetic block over registry warns dropped the matched warn and echoed the winner twice. Display now selects by id; core Matches untouched (the corpus warn-rate gate reads it). Test: TestGuardCheckAlsoMatchedListsTheNonWinners.
  • iss-347readTranscript was the one guarded read left off fsutil.ReadGuarded — no O_NOFOLLOW (a symlinked transcript_path was followed and persisted; reproduced) and no cap+1 probe (a growing file stored silently truncated against spc-4's refuse-whole invariant). Test: TestHookSessionEndRefusesSymlinkedTranscript.
  • iss-201 (pre-existing record, closed) — readHookInput and the guard hook read exactly the cap, misreporting an over-cap payload as malformed host JSON; both now read cap+1 and name the cap, mirroring guardCandidate. Tests: TestHookPromptRouterReportsAnOverCapPayload (+ at-cap boundary case), TestGuardHookReportsAnOverCapPayload.
  • iss-348 — the root command hid the shipped record-id dispatch (Usage: abcd, no Long); the sole command in the tree with an undeclared positional. Use/Long now state it; reference regenerated; spc-26 golden tests green.
  • iss-349docs/reference/terminology.md listed the pre-tool-use shell guard among "deterministic, fail-closed gates" — the opposite of adr-42's fail-open-loud mistake filter, on the page written for an evaluator, in the sentence citing enforcement claims are facts.
  • iss-350 — the README promised bare abcd ahoy reports the off-PATH gap after the copy one-liner install; reproduced both branches (no plugin root → no PATH gap at all; resolvable root → symlink.foreign, resolve-manually). The sentence now names the installer's reachability note, the thing that actually prints the fix.
  • iss-351 — six sites hand-rostered "the three disciplines (itd-1, itd-5, itd-37)" while intents/disciplines/ holds six; the sites now point at the directory, and the genuinely phase-scoped mentions say so.
  • iss-352 — three records cited ADRs at development/research/adr/ (never existed; adr-30 rejected the layout), invisible to links_resolve as code spans; repointed as markdown links the blocker rule gates. adr-30's own rejected-alternative mention untouched.
  • iss-353research/notes/README.md prescribed undated <topic>-<kind> naming against adr-30, development/README.md, the record-lint successor path, and the ideate writer (29 of 41 notes are dated); the outlier section rewritten, nothing renamed.
  • iss-354 — no test coupled ci.yml's three go-version pins to AbcdSubstitutions().GoVersion (the d594511→8b70d70 drift shipped CI-green binaries on an unpatched toolchain). TestWorkflowGoVersionsMatchSubstitutions added, verified by mutation.
  • iss-356 — grouped nitpick batch, all thirteen items fixed: guard-help backtick disclosure (iss-148 remains the parser record), urlguard RFC 6890 ranges incl. CGNAT 100.64/10, repolint not-scanned warn for oversize textual files, 64-hex --commit, launch --dry-run required Short, release.yml --verify-tag/rehearsal wording (+ scaffold templates, byte parity held), dependabot pip ecosystem for /docs, five-of-nine chapter rosters, phase-6/06-lint change-narration blocks, phantom ledger citation, retired activity-record noun.

Built, reviewed, reverted — wontfix (iss-358)

The scanner hostname trailing-\b fix (lanHostRe/deviceHostRe, the residual of hunt A's iss-307 sweep) was implemented with a watched-fail test and then reverted on this PR's own pre-merge adversarial review, which proved the loosened pattern flags ordinary snake_case selectors — stream.local_addr (Rust), args.local_rank (Python), storage.local_key (JS) — and Stage-1 redaction rewrites every finding into the history store, the one irreversible artefact abcd writes. The warn-tier miss on an underscore-suffixed hostname is the accepted cost; TestSnakeCaseSelectorsStayQuiet pins the decision, and the record annotates why the original whole-tree no-regression sweep was insufficient evidence.

Confirmed substantive, recorded not fixed (stays open)

  • iss-355 — a batched merge-queue push makes github.sha the batch tip, so auto-release tags the wrong commit and release.yml's HEAD^2^ receipt derivation resolves an unrelated PR's commit whenever the release roll is not the last entry in its batch; the ancestry guard passes and the run wedges after the immutable tag — iss-326's outcome by a route the itd-93 pre-merge check cannot close (batch composition is the queue's decision). Proven from this repo's own merge history and auto-release run numbering. Required-check workflow logic, unverifiable without a live Actions run (the iss-301/302 precedent), so the record carries two proposed fixes: a receipts-presence-searching derivation (fail-closed before any tag, mirrored into a batch-simulating rehearsal), or max_entries_to_merge: 1.
  • iss-357 — memory ingest records a redirect-controlled FinalURL verbatim into stored material origins: the iss-359 class one door over, recorded for a future round.

Considered and rejected (refuted candidates)

  • The external-review required check wedging under pull_request_target — refuted empirically: check runs attach to the PR head sha. Incidental observation for iss-302: role_of returned admin under read-only perms, so its severity clause about 403s did not reproduce.
  • The docs site being "outside every gate" — docs-lint gates docs/ on every PR; the inert classifier and dashboard build are recorded decisions (iss-234, wrangler.jsonc).
  • Empty how-to/tutorials/reference promises — genre statements of a committed Diátaxis IA; the gaps are owned (iss-216, the facilitator plan).
  • Email case-folding at the identity pin — byte-exact parity with the sh pre-commit gate is a documented cross-implementation contract; folding would be a fail-open loosening.
  • The guard's top-level backtick silent allow as a fix target — open prior art (iss-148) with a reverted fix behind it; only its missing help-text disclosure was new (fixed under iss-356).
  • resolvedSHA 64-hex widening — prior art iss-206, and GitHub has no SHA-256 object format on this value's path.
  • A .PHONY completeness fix — inert for the targets named.

Dedup boundary

iss-307, iss-311, iss-317, iss-319, iss-321, iss-325 (hunt A's reconciliation) and the v0.6.0 wedge (iss-326/327, dispositioned into itd-93) were already held by the sibling loop or the maintainer and were not re-reported; iss-358 is the declared residual of iss-307's sweep, and iss-355 is the batch-composition route iss-326/327 do not cover.

Round 2 of the bug-hunt loop (state: #368). Gates: make preflight + gofmt -l . + scripts/check-attribution.sh green locally on the merged tree; CHANGELOG entries cite their iss-N handles; round summary appended to .abcd/work/DECISIONS.md.

Assisted-by: Claude:claude-fable-5

REPPL added 21 commits August 20, 2026 07:19
Twelve substantive findings and one grouped nitpick batch, each
adversarially refuted before capture. iss-355 is recorded, not fixed
(required-check workflow logic, unverifiable without a live Actions
run); the rest are fixed in the commits that follow.

Assisted-by: Claude:claude-fable-5
lanHostRe and deviceHostRe kept the trailing ASCII word boundary the
token patterns and ipv4Re/macRe already retired, so a hostname followed
by '_' or an alnum silently dropped the whole match. The boundary's one
useful side effect — suppressing a truncated longer label — moves to a
positional truncatedLabel skip. Watched failing: three suffixed
hostnames scanned to zero findings before the change.

Assisted-by: Claude:claude-fable-5
Matches is ordered blockers, warns, synthetics — not winner-first — so
when a synthetic block wins over registry warns, rendering Matches[1:]
dropped the matched warn and echoed the winner twice. Watched failing:
git clean chained with an env -S payload reported only the winner.

Assisted-by: Claude:claude-fable-5
readTranscript was the one guarded read left off fsutil.ReadGuarded: no
O_NOFOLLOW, so a symlink at the hook-supplied transcript_path was
followed and the target's bytes persisted to the history store, and no
cap+1 probe, so a file crossing the cap between stat and read stored a
silently truncated prefix against spc-4's refuse-whole invariant.
Watched failing: a symlinked transcript was stored as a record.

Assisted-by: Claude:claude-fable-5
readHookInput and the guard hook read exactly the cap, so an over-cap
payload was truncated into a severed prefix and misreported as
malformed host JSON — the guard fail-open blamed the host and the
prompt router injected nothing with the wrong reason. Both now read
cap+1 and name the cap, mirroring guardCandidate. Watched failing: an
over-cap prompt-router payload reported 'unexpected end of JSON input'.

Assisted-by: Claude:claude-fable-5
…iss-345)

net/url preserves raw non-ASCII in a Location query and encoding/json
escapes neither C1 nor bidi/zero-width runes, so a hostile redirect
could land a terminal escape or Trojan-Source override raw in docs cite
refresh --json and in the committed citations baseline. The fetch
boundary now percent-encodes every rune the terminal sanitizer would
mask (lossless, so the record round-trips), the baseline validator
refuses such a final_url from any producer, and the false JSON-needs-no-
sanitising claims in termsafe and the lifeboat coverage report are
corrected and pinned by a test. Watched failing: a redirect target
carrying U+009B/U+202E/U+200B was recorded raw.

Assisted-by: Claude:claude-fable-5
…-356)

Go's IsPrivate covers RFC 1918 + ULA only, so CGNAT 100.64/10 — live
internal addressing on Tailscale tailnets and carrier/cloud networks —
passed every predicate, contradicting the package's own promise that
internal services are out of reach. The RFC 6890 table closes the class
in one place, and the NAT64/6to4 unwrap re-checks it for free. Watched
failing: eight reserved addresses were fetchable.

Assisted-by: Claude:claude-fable-5
privacy-hygiene silently skipped any tracked file over the 4 MiB cap
and then reported the repo conforming — content nobody scanned, the
didn't-scan-reported-clean shape the engine contract at repolint.go
forbids. A skipped file that looks textual (first-8-KiB NUL probe, the
same heuristic isBinary uses) now yields a not-scanned warn naming the
file; binary assets stay quiet. Watched failing: the fixed test failed
against the stashed pre-change rule.

Assisted-by: Claude:claude-fable-5
reCommitSha stopped at 40 hex chars, refusing a SHA-256 repo's rev-parse
output against spc-25's own never-refuse-a-legitimate-resolution
rationale; the sibling receipt gate already accepts {7,64}. The flag
help, plugin command page, spec, and intent record move with the
surface. Watched failing: a 64-hex --commit was refused as malformed.

Assisted-by: Claude:claude-fable-5
Three help surfaces understated the binary: the root command hid the
shipped record-id dispatch (Usage said bare 'abcd' takes no positional
and --help never mentioned it — the sole command in the tree with an
undeclared positional), launch's Short read as if --dry-run were
optional while the bare form refuses, and the guard check gap list
omitted the disclosed backtick limit that internal/README claims is
stated there (iss-148 stays the tracking record for the parser gap
itself). The generated reference moves with the tree.

Assisted-by: Claude:claude-fable-5
…ons (iss-354)

TestSelfScaffoldParity gates release.yml and auto-release.yml through
the templates, but no test read ci.yml, whose three pins float free —
the d594511 incident (CI green on a patched toolchain while the release
built on the unpatched one) would recur silently on the next bump.
Verified by mutation: a ci.yml pin moved to 1.25.12 fails the test.

Assisted-by: Claude:claude-fable-5
…d pins (iss-356)

dependabot declared gomod and github-actions only, so
docs/requirements.txt had no update path at all, and the gitleaks
env-var version and zizmor run-block digest aged with nothing to say
so. The pip ecosystem gets the same seven-day cooldown; the two scanner
pins now state in place that their bumps are a deliberate maintainer
act rather than an oversight.

Assisted-by: Claude:claude-fable-5
The publish step's comment claimed --verify-tag checks the tag points
at the checked-out commit; gh checks remote existence only, exactly as
the contract note at the top of the same file states. The rehearsal
header said a dispatch runs ONLY the rehearsal job (verify runs too,
having no event gate) and its final echo claimed the full gate armed
when the job runs the resolution plus record-lint and a build. Both
files move together under the byte-parity test.

Assisted-by: Claude:claude-fable-5
…iss-349)

The Guardrails row listed the pre-tool-use guard among deterministic,
fail-closed gates — the opposite of adr-42's fail-open-loud mistake
filter, asserted on the one page written for an evaluator and in the
same sentence that cites enforcement-claims-are-facts. The lint,
receipt, and pre-commit gates keep their fail-closed billing; the guard
now carries its own.

Assisted-by: Claude:claude-fable-5
The README said bare 'abcd ahoy' reports the off-PATH state as a named
gap after the copy one-liner install. It does not: with no plugin root
the PATH detector returns nothing, and with one the copied binary reads
as a foreign file with a resolve-manually hint. The verb that actually
prints the export line for a fresh user is ahoy install's reachability
note, so the sentence now names it.

Assisted-by: Claude:claude-fable-5
Six sites enumerated 'the three disciplines (itd-1, itd-5, itd-37)'
while intents/disciplines/ holds six active disciplines — dropping the
persona-registry, judge-calibration, and intent-decomposition gates
from the mental model, scope, roadmap dashboard, and phase-0 record.
The roadmap's own banner above the stale row says counts are derived
from the filesystem, never hand-kept, so the sites now point at the
directory; the genuinely phase-scoped mentions say so.

Assisted-by: Claude:claude-fable-5
Two brief chapters and the itd-37 discipline cited ADRs at
development/research/adr/ — a directory that has never existed and a
layout adr-30 explicitly rejected; all forty ADRs live under
decisions/adrs/. The three citations were backticked code spans, which
links_resolve never reads, so they are now markdown links the blocker
rule gates. adr-30's own mention of the rejected path stays: that one
is intended narration.

Assisted-by: Claude:claude-fable-5
notes/README.md prescribed <topic>-<kind>.md with no date slot while
adr-30, development/README.md, the record-lint successor path, and the
ideate writer all say dated — and 29 of the 41 notes on disk are. The
outlier rule yields: the stated form is now the dated one, the kind
vocabulary demotes to an optional suffix, and the pre-adr-30 undated
files are declared legacy rather than renamed.

Assisted-by: Claude:claude-fable-5
…e pointers (iss-356)

Four pointer lines still enumerated five of the nine 05-internals
chapters (the tree line and the chapter index stay as the single
rosters); phase-6 carried three withdrawn-claims blocks and 06-lint a
formerly-pending parenthetical, all narrating prior text against the
record's present-tense-only rule while every fact they carried already
stands elsewhere; 05-internals cited a ledger entry that has never
existed; and 03-evidence named the adr-32-retired activity record where
its sibling chapter already says the working record. The bug-hunting
playbook's open-frontier item 0 is struck: iss-101 fixed it.

Assisted-by: Claude:claude-fable-5
The corrected sentence spelled out 'abcd ahoy install', which the
bootstrap test rightly rejects in the README: the reader it addresses
does not have abcd on PATH yet. 'The installer' is the form the
surrounding prose already uses.

Assisted-by: Claude:claude-fable-5
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
abcd 7aaef13 Aug 20 2026, 08:54 AM

REPPL added 3 commits August 20, 2026 08:05
…vidence

The pre-merge adversarial review proved the loosened lanHostRe flags
ordinary snake_case selectors — stream.local_addr (Rust),
args.local_rank (Python), storage.local_key (JS) — and Stage-1
redaction rewrites every finding into the history store, the one
irreversible artefact abcd writes. That trade (a warn-tier miss on an
underscore-suffixed hostname against corrupting real code in
transcripts) goes the other way, so the change is reverted, iss-344
moves to wontfix with the measured cost, and
TestSnakeCaseSelectorsStayQuiet pins the decision.

Assisted-by: Claude:claude-fable-5
encodeHiddenRunes iterated runes, so an invalid UTF-8 byte in a
redirect query was rewritten to U+FFFD instead of percent-encoded —
lossy exactly where the comment promised lossless. Byte-wise decoding
keeps the raw byte as %XX; clean and already-encoded inputs are
untouched, pinned by tests.

Assisted-by: Claude:claude-fable-5
…357)

The urlguard test's non-reserved IP literals carry the audit waivers
the adjacent fixture already uses (the repo's own lint is back at its
main baseline), and the reserved-range table says why the RFC 5737
documentation blocks stay reachable. The repolint oversize probe warns
instead of staying silent when it cannot read, and its message derives
the cap. The hook over-cap message says the payload was discarded
unparsed rather than 'nothing was read'. The rehearsal comment reflows
to the block's width in the workflow and its template. iss-355's
citations name the exact assert line and the ruleset mirror's path.
iss-357 records the memory-ingest FinalURL sibling site the cite fix
did not reach. The round log reflects the iss-344 reversal.

Assisted-by: Claude:claude-fable-5
REPPL added 3 commits August 20, 2026 08:15
A clean whole-tree sweep is evidence about that tree, not the input
space — the sentence claiming no-regression verification is exactly
what failed to see the snake_case class, and the record should say so.

Assisted-by: Claude:claude-fable-5
Resolves the guard help gap list (both new disclosure lines kept) and
regenerates the CLI reference with the repo's own tooling.

Assisted-by: Claude:claude-fable-5
…-359)

Main landed its own iss-344 and iss-345 while this round was in flight
— the concurrent-mint collision the ledger's new iss-344 itself records
— so this round's two records are re-minted with fresh captures and
their statuses reapplied (iss-358 wontfix, iss-359 resolved); every
reference in code, tests, CHANGELOG, and the round log moves with them.
Ids are never hand-edited.

Assisted-by: Claude:claude-fable-5
@REPPL
REPPL force-pushed the bughunt-b/round-2 branch from fd068ee to 7aaef13 Compare August 20, 2026 08:53
@REPPL REPPL changed the title fix: bughunt round 2 — iss-344..iss-356 captured, twelve fixed, iss-201 closed fix: bughunt round 2 — eleven records fixed, iss-201 closed, iss-358 wontfixed on review evidence Aug 20, 2026
@REPPL
REPPL enabled auto-merge August 20, 2026 08:55
@REPPL
REPPL added this pull request to the merge queue Aug 20, 2026

REPPL commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Merge gate — dual independent review + CI

CI on head 7aaef13b: all 13 checks green — check (macos + ubuntu), record-lint, smoke, gitleaks, zizmor, attribution, external-review, govulncheck, changes, dependency-review, Workers Builds. (make preflight, gofmt -l ., abcd lint at its main baseline, and check-attribution.sh also green locally on the merged tree.)

Reviewer A (Claude Fable 5): SHIP. Mutation-tested every new test by reverting each fix (none vacuous), verified the 51-entry committed baseline against the new validate rung, confirmed the ledger's capture-before-fix ordering and iss-355's evidence against the live workflows. Two concerns, both acted on: encodeHiddenRunes was lossy on invalid UTF-8 (now byte-wise, tested), and the memory-ingest FinalURL sibling site was unrecorded (now iss-357).

Reviewer B (Claude Opus 5): NO-SHIP — two blockers, both accepted as correct:

  1. The scanner hostname-boundary fix flagged ordinary snake_case selectors (stream.local_addr, args.local_rank, storage.local_key) and Stage-1 redaction would have rewritten them into the irreversible history store. Response: reverted; iss-358 wontfixed with the measured false-positive cost; TestSnakeCaseSelectorsStayQuiet pins the decision; the record annotates why the original whole-tree sweep was insufficient evidence.
  2. The new urlguard test added six unwaivered IP literals, doubling the repo's own abcd lint errors. Response: waivers added; the dogfood lint is back at its main baseline (verified against a clean checkout of main), with a negative control proving the waivers are load-bearing rather than a rule loosening.
    All of B's concerns and nits were also addressed (byte-wise encoding, repolint probe error path, over-cap message wording, rehearsal comment reflow, iss-355 citation precision).

Delta verification (independent third reviewer): BLOCKERS-CLOSED — the revert is byte-exact against the pattern's prior state, the selectors round-trip unmodified through ScanText+Redact, the regression pin fails when the reverted change is restored, and the full gate is clean including the race leg.

Also handled mid-gate: the merge conflict after main absorbed hunt A's reconciliation (resolved by keeping both new guard-help disclosure lines and regenerating the CLI reference with the repo's tooling), the concurrent-mint id collision (iss-344/345 re-minted as iss-358/359, references moved, ids never hand-edited), and the commit-identity correction the attribution gate's own remedy prescribes.

Auto-merge has been enabled by the maintainer; per the loop protocol this round performs no merge action itself.

Assisted-by: Claude:claude-fable-5


Generated by Claude Code

Merged via the queue into main with commit 286a257 Aug 20, 2026
12 of 13 checks passed
@REPPL
REPPL deleted the bughunt-b/round-2 branch August 20, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant