Skip to content

Add repository rules review bot - #568

Merged
terasakisatoshi merged 6 commits into
mainfrom
add-repository-rules-review-bot
Aug 4, 2026
Merged

Add repository rules review bot#568
terasakisatoshi merged 6 commits into
mainfrom
add-repository-rules-review-bot

Conversation

@terasakisatoshi

Copy link
Copy Markdown
Member

Implements the Phase 1 item in #566: "port tenferro's repository-rules-review.py + review_bot.yml (diff-scoped LLM review against REPOSITORY_RULES.md) with the section-routing convention". Same port as tensor4all/strided-rs#222, adapted to this workspace. Part of the environment work in shinaoka/task-management-terasaki#26.

What lands

File Role
.github/workflows/review_bot.yml Four jobs: LLM review, no-LLM skip, waiver, gate. Covers PRs to main and develop
scripts/repository-rules-review.py Diff chunking, secret gate, section routing, deterministic checks, DeepSeek call, diff-anchor filtering
scripts/test-repository-rules-review.py 54 tests, no pytest needed
ai/prompts/repository-rules-review.md System prompt
.github/actions/{post-review-comment,verify-review-label}/ Composite actions
scripts/requirements-dev.txt Local python-dotenv

AGENTS.md documents the bot and local usage; CI_rs.yml gains a scripts job running the script's tests, wired into rollup-rs. REPOSITORY_RULES.md is unchanged — both deterministic checks enforce rules this repository already states.

Security model

pull_request_target checks out the trusted base revision; the PR head is fetched for git diff only and never checked out or executed. External-fork PRs are rejected at the gate. Both label escape hatches require the maintain/admin role and reapplication after the latest push. Secret-shaped text in added lines blocks the upload before anything reaches the API.

Enforcement, not just prose

#566's structural diagnosis is that #552 ported tenferro's rule text but almost none of the enforcement layer. Two rules are exactly machine-checkable, so they run deterministically, before and independently of the LLM:

Check Rule #566 backlog
New direct tenferro-* dependency outside tensor4all-tensorbackend Dense Layout And Linear Algebra Phase 3: core, tcicore, simplett, treetci
Added ignore / no_run doctest fence Documentation Examples Phase 1: 2 no_run sites

Both are delta-scoped. A repo-wide gate would fail on the backlog today, so these stop new violations from landing while the backlog burns down separately — complementary to, not a replacement for, the check-crate-boundaries.py item in Phase 1.

The dependency check parses Cargo.toml table context rather than grepping, because tenferro-* appears both as feature names and as dependencies:

[features]
tenferro-cpu-faer = ["dep:tenferro-cpu"]   # feature name, not a violation

[dependencies]
tenferro-linalg.workspace = true            # violation outside tensorbackend

dev-dependencies are out of scope: the rule is about the runtime route, and test code may reach for tenferro fixtures directly.

Verified against the commits that introduced each class — 795c087 (added tenferro deps to core and treetci) and 21c2f07 (converted ignore fences to no_run) both produce the expected block, while 69a24e7 passes.

Section routing

All 26 rule sections are reachable. Base Branch Synchronization is marked human-only: it is a git-workflow protocol, so nothing inside a diff can satisfy or violate it, and showing it to a diff-scoped reviewer only invites invented findings. test_every_rule_section_is_reachable fails if a section is neither routed, always-on, nor explicitly human-only — so the sections Phase 2 will rewrite cannot go silently unreviewed.

A 26-file commit routes to 20 of 26 sections, which is where the cost saving comes from on a 463-line rules file.

Two-stack awareness

The prompt carries the #566 diagnosis that the network stack and the TCI stack differ in naming, option style, and error types by design. A difference between them is not by itself a finding. It also tells the reviewer that existing anyhow::Result surfaces and existing tenferro dependencies are known backlogs, reportable only when this diff adds to them.

Three fixes to the ported script

All found by a live run against 69a24e7, and all present in the tenferro original:

  1. socket.timeout escaped the exception tuple as an unhandled traceback. It only aliases TimeoutError from Python 3.10 on, so the ported (KeyError, ValueError, URLError, TimeoutError) missed it on 3.9. Because only stdout is teed into the report, the PR comment would have said "Review step did not produce a report" while the real error went to stderr. Catching OSError covers every version.
  2. Transient network failures now retry once before blocking a PR.
  3. A 108k single chunk timed out at the 120s default. Aggregate chunks are capped at 60k and the default timeout is 300s. The same commit now reviews as two chunks in 235s.

Fixes 1 and 2 are worth backporting to strided-rs and tenferro-rs.

Verification

  • 54 script tests pass; actionlint clean on both workflows
  • Dry runs: 795c087fail (dependency); 21c2f07fail (doctest); 69a24e7pass; waived → pass; no-LLM → one warn. Exit codes checked on all four paths
  • Live DeepSeek run on 69a24e7: 2 chunks, 235s, pass

Setup needed before this is useful

  • DEEPSEEK_API_KEY secret (optionally a DEEPSEEK_MODEL variable; the default is deepseek-v4-pro)
  • Labels rules-review:waive and rules-review:no-llm
  • Branch protection is intentionally untouched. Suggest watching one or two real PRs before considering review-bot-gate as a required check

Note that pull_request_target resolves the workflow from the base branch, so this PR does not exercise the bot on itself; the first PR after merge is the real test.

Refs #566

🤖 Generated with Claude Code

Port tenferro-rs's delta-scoped REPOSITORY_RULES review, per #566 Phase 1.
The workflow runs from the trusted base revision and treats PR contents as
data: the PR head is fetched for `git diff` only, never checked out or
executed. Findings post as a single updating PR comment; only block-severity
findings fail the check. It covers PRs to both main and develop.

#566's structural diagnosis is that #552 ported tenferro's rule text as prose
but almost none of the enforcement layer. Two rules are exactly machine
checkable, so they run deterministically, before and independently of the LLM:

- New direct `tenferro-*` dependency outside tensor4all-tensorbackend
  (Dense Layout And Linear Algebra). Cargo.toml names `tenferro-*` both as
  feature names and as dependencies, so the check tracks table context rather
  than grepping; dev-dependencies are out of scope.
- Added `ignore` / `no_run` doctest fence (Documentation Examples).

Both are delta-scoped. #566 records existing violations of each (Phase 3 for
the dependency route, Phase 1 for the two `no_run` sites), so a repo-wide gate
would fail on the backlog. These stop new violations while the backlog burns
down separately. Verified against the commits that introduced each class:
795c087 and 21c2f07 both produce the expected block.

Section routing covers all 26 rule sections. Base Branch Synchronization is
marked human-only: it is a git-workflow protocol, so nothing inside a diff can
satisfy or violate it, and showing it to a diff-scoped reviewer only invites
invented findings. A coverage test fails if a section is neither routed,
always-on, nor explicitly human-only, so the sections Phase 2 will rewrite
cannot go silently unreviewed.

The prompt carries the two-stack context from #566: the network stack and the
TCI stack differ in naming, option style, and error types by design, so a
difference between them is not by itself a finding.

Three fixes to the ported script, all found by a live run against 69a24e7:

- `socket.timeout` escaped the exception tuple as an unhandled traceback.
  It only aliases TimeoutError from Python 3.10 on, so the ported
  `(KeyError, ValueError, URLError, TimeoutError)` missed it on 3.9. Catching
  OSError covers every version, and the report reaches the PR comment instead
  of a stack trace reaching stderr.
- Transient network failures now retry once before blocking a PR.
- A 108k single chunk timed out at the 120s default. Aggregate chunks are
  capped at 60k and the default timeout is 300s; the same commit now reviews
  as two chunks in 235s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first pass caught OSError, which covers socket.timeout, TimeoutError,
ConnectionResetError, ssl.SSLError, and urllib.error.URLError.
http.client.IncompleteRead is a sibling of OSError rather than a subclass,
so a truncated chunked response still escaped. Name both roots in one
TRANSPORT_ERRORS tuple and assert the coverage in a test.

Keeps this file identical to the tenferro-rs and strided-rs copies.

Refs #566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d11fa03bb9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/repository-rules-review.py Outdated
Comment on lines +75 to +79
r"(?i)\b("
r"[\w.-]*(?:api[_-]?key|token|secret|password|passwd|pwd|client[_-]?secret|"
r"private[_-]?key)[\w.-]*"
r"\s*[:=]\s*)"
r"([^\s#]+)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detect secrets in typed declarations before upload

When a PR adds a typed declaration such as const API_KEY: &str = "abcdefghijklmnopqrst", SECRET_ASSIGNMENT treats the type colon as the assignment separator and redacts only &str, while QUOTED_SECRET_ASSIGNMENT does not match through the type annotation. The secret literal therefore survives redact_file_diffs, sensitive_diff_finding does not block the request, and the credential is uploaded to the external LLM despite the documented pre-upload guard. Handle typed declarations conservatively in both detection and redaction.

AGENTS.md reference: AGENTS.md:L285-L292

Useful? React with 👍 / 👎.

Comment on lines +293 to +294
if worktree:
output = run_git(["diff", "--name-only", base])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include untracked files in worktree previews

When the documented local preview is run before a newly created file has been staged, git diff --name-only <base> omits that untracked file, so neither the deterministic checks nor the LLM review inspect it and the preview can incorrectly report no findings. The official git diff documentation describes this form as comparing the working tree with a commit; an untracked path has no compared Git object. Add untracked paths and their contents to worktree-mode input or clearly require staging first.

AGENTS.md reference: AGENTS.md:L275-L280

Useful? React with 👍 / 👎.

Comment on lines +481 to +483
hunk_header = hunk[0]
body = hunk[1:]
prefix = [*header, hunk_header]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve line offsets when splitting oversized hunks

When one diff hunk exceeds MAX_FILE_DIFF_CHARS, every emitted chunk repeats the original hunk header even though later chunks begin thousands of lines farther into the new file. The model therefore derives incorrect line numbers for findings in later chunks; filter_findings may either discard a real issue or retain it against an unrelated earlier added line. Rewrite each repeated hunk header with the chunk's actual old/new offsets, or otherwise provide explicit source line numbers.

AGENTS.md reference: AGENTS.md:L269-L273

Useful? React with 👍 / 👎.

@terasakisatoshi
terasakisatoshi enabled auto-merge (squash) August 4, 2026 01:57
Three findings from the Codex review of this PR, plus the root cause of the
failure the bot itself hit on tensor4all/strided-rs#223.

**Typed declarations hid secrets from the pre-upload guard (P1).** For
`const API_KEY: &str = "..."`, `SECRET_ASSIGNMENT` treated the type colon as
the separator and redacted `&str`, leaving the literal intact, while
`QUOTED_SECRET_ASSIGNMENT` did not match through the annotation at all. The
credential therefore survived redaction and was uploaded to the external LLM.
Both patterns now allow a short type annotation between the name and the
value.

The detector's own fixtures were themselves secret-shaped, so this file
tripped the improved guard and would have blocked the LLM pass on every PR
touching its tests. Fixtures are now assembled at runtime; the file contains
no contiguous secret-shaped literal while the tests still exercise the real
shapes.

**Oversized hunks reported wrong line numbers (P2).** Every chunk repeated the
original hunk header even though later chunks start thousands of lines
further into the file, so `filter_findings` either dropped a real finding or
kept it against an unrelated added line that happened to collide. Each chunk
now carries a header rewritten to its own old/new offsets, counting context,
removal, and addition lines separately. An unparseable header falls back to
the previous verbatim behaviour rather than inventing offsets.

**Worktree previews skipped untracked files (P2).** `git diff <base>`
compares the working tree against a commit, so a newly created file has no
object to compare and was omitted from the documented local preview
entirely -- a brand new file was reviewed by nothing and the preview reported
a false pass. Untracked paths are now enumerated with `ls-files --others
--exclude-standard` and diffed against /dev/null with `--no-index`, which
needs no staging and does not touch the index.

**An unusable API key was reported as unparseable JSON.** HTTP header values
are latin-1, so a key carrying non-ASCII text raises UnicodeEncodeError before
any request leaves the machine. That is a ValueError subclass, so it surfaced
as "External LLM review did not produce usable JSON" and pointed the reader at
the model instead of at the secret. The key is now validated up front with a
diagnostic that names the offending offsets without echoing the value, and the
generic wording no longer claims the model responded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
Ported from the Codex review of tensor4all/tensor4all-rs#568, which reviewed
the same script. All three findings apply verbatim here.

Typed declarations hid secrets from the pre-upload guard. For
`const API_KEY: &str = "..."`, SECRET_ASSIGNMENT treated the type colon as the
separator and redacted the type, leaving the literal, while
QUOTED_SECRET_ASSIGNMENT did not match through the annotation at all, so the
credential was uploaded. Both patterns now allow a short type annotation.
The detector fixtures were themselves secret-shaped and tripped the improved
guard, so they are assembled at runtime now.

Oversized hunks reported wrong line numbers: every chunk repeated the original
header, so findings in later chunks came back with line numbers thousands of
lines too small. Each chunk now carries a header rewritten to its own offsets.

Worktree previews skipped untracked files entirely, so a brand new file was
reviewed by nothing and the documented preview reported a false pass.
Untracked paths are diffed against /dev/null with --no-index.

An unusable API key was reported as unparseable JSON. This is the failure the
bot hit on strided-rs#223: a non-ASCII key raises UnicodeEncodeError while
encoding the latin-1 Authorization header, and that is a ValueError subclass,
so it surfaced as "did not produce usable JSON". The key is validated up front
now, with a diagnostic that names the offending offsets without echoing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Refs #199
terasakisatoshi added a commit to tensor4all/tenferro-rs that referenced this pull request Aug 4, 2026
Ported from the Codex review of tensor4all/tensor4all-rs#568, which reviewed
the same script. All three findings apply verbatim here.

Typed declarations hid secrets from the pre-upload guard. For
`const API_KEY: &str = "..."`, SECRET_ASSIGNMENT treated the type colon as the
separator and redacted the type, leaving the literal, while
QUOTED_SECRET_ASSIGNMENT did not match through the annotation at all, so the
credential was uploaded. Both patterns now allow a short type annotation.
The detector fixtures were themselves secret-shaped and tripped the improved
guard, so they are assembled at runtime now.

Oversized hunks reported wrong line numbers: every chunk repeated the original
header, so findings in later chunks came back with line numbers thousands of
lines too small. Each chunk now carries a header rewritten to its own offsets.

Worktree previews skipped untracked files entirely, so a brand new file was
reviewed by nothing and the documented preview reported a false pass.
Untracked paths are diffed against /dev/null with --no-index.

An unusable API key was reported as unparseable JSON. This is the failure the
bot hit on strided-rs#223: a non-ASCII key raises UnicodeEncodeError while
encoding the latin-1 Authorization header, and that is a ValueError subclass,
so it surfaced as "did not produce usable JSON". The key is validated up front
now, with a diagnostic that names the offending offsets without echoing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Also removes a stale duplicate QUOTED_SECRET_ASSIGNMENT that sat after
SEVERITY_ALIASES in this copy and silently overrode the new definition.
@terasakisatoshi

Copy link
Copy Markdown
Member Author

All three Codex findings were real and are fixed in the latest commit. Ported to the other two copies of this script: tensor4all/strided-rs#223 and tensor4all/tenferro-rs#1603.

P1 — typed declarations hid secrets from the pre-upload guard

Confirmed. For const API_KEY: &str = "...", SECRET_ASSIGNMENT matched the type colon as its separator and redacted &str, leaving the literal intact; QUOTED_SECRET_ASSIGNMENT required a quote immediately after the separator, so it did not match through the annotation at all. The credential survived both and was uploaded.

Both patterns now accept a short type annotation between the name and the value. The existing negative tests (std::env::var("DEEPSEEK_API_KEY"), ${{ secrets.* }}, api_key: Option<String>,) still pass, so the relaxation did not turn into a false-positive machine.

Second-order problem this exposed: the detector's own test fixtures are secret-shaped, so this file tripped the improved guard and every future PR touching its tests would have been blocked with only a maintainer waiver as recourse. Fixtures are now assembled at runtime from fragments — the source contains no contiguous secret-shaped literal, while the tests still exercise the real shapes. Verified by scanning both scripts with contains_sensitive_text: zero hits, and the bot's own --worktree preview of this branch now reports pass.

P2 — oversized hunk offsets

Confirmed, including the sharper half of the claim: a wrong line number does not merely get dropped by filter_findings, it can collide with an unrelated added line and be retained against it.

Each emitted chunk now carries a header rewritten to its own old/new offsets, counting context, removal, and addition lines separately (\ No newline at end of file advances neither). A header this script cannot parse falls back to the previous verbatim behaviour rather than inventing offsets. Tests assert that chunk starts chain (start + count == next_start) and that the counts sum to the original hunk.

This also corrected two tenferro-rs tests that had pinned the old repeat-verbatim behaviour as intended.

P2 — untracked files in worktree previews

Confirmed, and I had already been bitten by it without noticing: an earlier --worktree smoke test of this branch reported "no findings" while every new file in the PR was untracked and therefore invisible.

Untracked paths are now enumerated with git ls-files --others --exclude-standard and diffed against /dev/null with --no-index, which requires no staging and does not touch the index. Verified by dropping an untracked file containing a no_run doctest into the tree; the deterministic check now catches it, and it disappears when the file is removed.

Also fixed: the failure the bot hit on strided-rs#223

Not from this review, but the same file. The bot's first live run blocked with:

UnicodeEncodeError: 'latin-1' codec can't encode characters in position 35-39

reported as "External LLM review did not produce usable JSON". HTTP header values are encoded latin-1, so a key carrying non-ASCII text raises before any request leaves the machine — and UnicodeEncodeError is a ValueError subclass, so it landed in the JSON-parsing bucket and pointed the reader at the model instead of at the secret. The key is now validated up front with a diagnostic naming the offending offsets without echoing the value, and the generic wording no longer claims the model responded.

Verification

66 script tests pass here (61 in strided-rs, full suite in tenferro-rs), actionlint clean, and each repository's copy now reviews its own branch cleanly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bd6b21075

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/repository-rules-review.py Outdated
Comment thread scripts/repository-rules-review.py Outdated
TENFERRO_ROUTE_CRATE = "tensor4all-tensorbackend"
TENFERRO_DEP_TABLES = frozenset({"dependencies", "build-dependencies"})
CARGO_TABLE = re.compile(r"^\[([^\]]+)\]\s*$")
TENFERRO_DEP_KEY = re.compile(r"^\s*(tenferro-[\w-]+)\s*(?:\.|=)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse Cargo package aliases in the dependency gate

When a crate uses valid Cargo renaming such as linalg = { package = "tenferro-linalg", ... }, a quoted key, or [dependencies.tenferro-linalg], this regex does not identify the package as tenferro, so the advertised deterministic boundary check passes. Because the prompt tells the LLM not to duplicate this deterministic check, such a direct dependency can land entirely undetected; parse TOML package values and dependency subtables rather than relying only on a bare key prefix.

AGENTS.md reference: AGENTS.md:L291-L297

Useful? React with 👍 / 👎.

Comment thread scripts/repository-rules-review.py Outdated
Comment on lines +325 to +326
output = run_git(["diff", "--name-only", f"{base}...{head}"])
return [line.strip() for line in output.splitlines() if line.strip()]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decode Git pathnames before reviewing them

When a PR changes a non-ASCII pathname such as 日本.md, Git's default --name-only output is C-quoted (for example, "\346...md"), and this code treats that representation as the literal filesystem path; per_file_diffs therefore produces no LLM chunk and extension-based deterministic checks do not recognize the file. Local git diff -h confirms that -z emits NUL-terminated raw names, so consume that form or otherwise unquote paths to ensure every changed file is reviewed.

AGENTS.md reference: AGENTS.md:L269-L272

Useful? React with 👍 / 👎.

Comment thread scripts/repository-rules-review.py
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
* Fix review bot network timeout handling

A live run in the tensor4all-rs port of this script surfaced three problems,
all inherited from the tenferro original.

`socket.timeout` escaped the exception tuple as an unhandled traceback. It
only aliases `TimeoutError` from Python 3.10 on, so
`(KeyError, ValueError, URLError, TimeoutError)` misses it on 3.9. This
matters beyond the version gap: the workflow tees only stdout into the
report, so the traceback went to stderr and the PR comment would have read
"Review step did not produce a report" with no diagnostic. Catching `OSError`
covers socket.timeout, TimeoutError, and URLError on every version, so the
existing `llm-review-unusable` block finding reaches the comment instead.

Transient network failures now retry once before blocking a PR. One blocked
PR per network blip is not an acceptable failure mode for a required-adjacent
check.

A 108k single chunk timed out against DeepSeek at the 120s default. Aggregate
chunks are capped at 60k and the default timeout is 300s. Per-chunk latency
measured here was 26-150s depending on size, so 300s leaves real margin.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Cover ConnectionResetError, SSLError, and IncompleteRead

The first pass caught OSError, which covers socket.timeout, TimeoutError,
ConnectionResetError, ssl.SSLError, and urllib.error.URLError.
http.client.IncompleteRead is a sibling of OSError rather than a subclass,
so a truncated chunked response still escaped. Name both roots in one
TRANSPORT_ERRORS tuple and assert the coverage in a test.

Keeps this file identical to the tenferro-rs and tensor4all-rs copies.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address Codex review: secret guard, hunk offsets, untracked previews

Ported from the Codex review of tensor4all/tensor4all-rs#568, which reviewed
the same script. All three findings apply verbatim here.

Typed declarations hid secrets from the pre-upload guard. For
`const API_KEY: &str = "..."`, SECRET_ASSIGNMENT treated the type colon as the
separator and redacted the type, leaving the literal, while
QUOTED_SECRET_ASSIGNMENT did not match through the annotation at all, so the
credential was uploaded. Both patterns now allow a short type annotation.
The detector fixtures were themselves secret-shaped and tripped the improved
guard, so they are assembled at runtime now.

Oversized hunks reported wrong line numbers: every chunk repeated the original
header, so findings in later chunks came back with line numbers thousands of
lines too small. Each chunk now carries a header rewritten to its own offsets.

Worktree previews skipped untracked files entirely, so a brand new file was
reviewed by nothing and the documented preview reported a false pass.
Untracked paths are diffed against /dev/null with --no-index.

An unusable API key was reported as unparseable JSON. This is the failure the
bot hit on strided-rs#223: a non-ASCII key raises UnicodeEncodeError while
encoding the latin-1 Authorization header, and that is a ValueError subclass,
so it surfaced as "did not produce usable JSON". The key is validated up front
now, with a diagnostic that names the offending offsets without echoing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Refs #199

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Cargo renaming and dependency subtables bypassed the tenferro route check;
tilde doctest fences bypassed the doctest check; quoted secrets containing
spaces were neither detected nor fully redacted; git C-quoted non-ASCII
pathnames so those files were reviewed by nothing; path-only routing never
supplied the unsafe rules for a generic filename; and cumulative retries could
outlive the job timeout and lose the report.

Adds the work log this repository's Work Logs And Design Records rule requires,
with the classification ledger for both review rounds.

Refs #566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/tenferro-rs that referenced this pull request Aug 4, 2026
Batch of related findings against scripts/repository-rules-review.py: a live
run of the ported copy plus the Codex review of tensor4all/tensor4all-rs#568,
which reviewed this same shared code. Full classification ledger in the work
log. No production Rust changes.

Transport errors: the handler enumerated leaves and missed siblings.
`socket.timeout` only aliases TimeoutError from Python 3.10 on, and
ConnectionResetError, ssl.SSLError, and http.client.IncompleteRead were never
caught at all -- the last is a sibling of OSError, not a subclass. The failure
mode is worse than the crash: the workflow tees only stdout, so a traceback
went to stderr and the PR comment read "did not produce a report" with no
diagnostic while the gate failed. Name the two roots instead of a leaf list.

Retry budget: retrying once prevents a blocked PR per network blip, but at a
300s per-attempt timeout a three-chunk diff could exceed timeout-minutes: 20
and be killed mid-request, losing the report entirely. Bound all LLM traffic
to a 900s budget, clamp each request to the remaining time, skip a retry that
would cross the deadline, and warn rather than block when the budget runs out.

Secret guard: a typed declaration let the redactor mask the type and leave the
literal, and a quoted value containing spaces was neither detected nor fully
redacted -- most of a passphrase was uploaded. Widening the value pattern then
broke this repository's own token_type regression test, which is the point: a
diceware passphrase is prose by construction, so no value-shape heuristic can
separate the two. Move the discrimination to the name.

The guard's own fixtures then tripped it, so a maintainer waiver would have
been the only way to review any PR touching its tests. Fixtures are assembled
at runtime now; the source carries no contiguous secret-shaped literal.

Chunk headers: splitting an oversized hunk repeated the original @@ header, so
findings in later chunks came back with line numbers thousands of lines too
small and were dropped, or retained against an unrelated colliding line. Each
chunk is renumbered to its own offsets.

Routing: path-only routing never supplied the unsafe rules for an unsafe block
added under a generic filename, and the prompt forbids inventing requirements
that were not supplied, so the rule was unenforceable there. Add content
triggers. This exposed that HUMAN_ONLY_SECTIONS was never subtracted here --
the "intentionally not routed to the diff-scoped review bot" guarantee in
Performance-Gated Experiment Protocol held only because no trigger named one.

Also: git C-quotes non-ASCII pathnames by default, so such a file was reviewed
by nothing; and a stale duplicate QUOTED_SECRET_ASSIGNMENT after
SEVERITY_ALIASES silently overrode the new definition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@terasakisatoshi

Copy link
Copy Markdown
Member Author

Second review round: all five findings were real and are fixed, and ported to tensor4all/strided-rs#223 and tensor4all/tenferro-rs#1603. Each was reproduced against the working tree before changing anything.

P1 — Cargo package aliases in the dependency gate

Confirmed. The bare-key regex saw only one of three valid declaration forms:

[features]
tenferro-cpu-faer = ["dep:tenferro-cpu"]      # not a dependency

[dependencies]
linalg = { package = "tenferro-linalg" }       # renaming — was invisible
"tenferro-tensor" = { workspace = true }       # quoted key — was invisible
[dependencies.tenferro-einsum]                 # subtable — was invisible

tomllib needs Python 3.11 and CI runs ubuntu-22.04 (3.10), so this stays a line-oriented parser, but it now tracks table paths properly: dependency_table_of handles [target.'cfg(unix)'.dependencies.<name>], quoted keys are unquoted, and package = "tenferro-*" is matched inside a dependency entry. Feature names and dev-dependencies remain out of scope by construction, asserted in the test.

P1 — redact complete assigned secrets

Confirmed, and worse than described in one respect: the redactor left password = [REDACTED_SECRET] horse battery staple" — the mask itself made the leak look handled.

Widening the value pattern to allow spaces then broke a regression test tenferro-rs already had for token_type: "WebGPU event token from another queue". That test earned its keep and it settles the design question: a diceware passphrase is prose by construction, so no value-shape heuristic can separate a credential from a description. The discrimination moved to the name — is_credential_name rejects identifiers ending in _type, _name, _path, _id, _kind, and similar metadata suffixes, in both detection and redaction. An assignment opening a quote that closes on a later line is treated as disqualifying, covering the multiline case you raised.

P2 — tilde doctest fences

Confirmed; ~~~rust,no_run is valid CommonMark and rustdoc honours the attribute. The fence pattern now accepts ``` and ~~~.

P2 — decode Git pathnames

Confirmed. Rather than switching call sites to -z, every git invocation now passes -c core.quotePath=false, which fixes --name-only, the +++ b/... headers, and any future call in one place.

P2 — route rules using changed content

Confirmed, and this was the most consequential one: the prompt explicitly forbids inventing requirements that were not supplied, so a rule that never got routed was not merely unlikely to be enforced, it was unenforceable. Added CONTENT_TRIGGERS, matched against the added lines themselves (unsafe, anyhow::Result, to_dense(, tenferro_*::, .id(), OnceLock, t4a_*, …).

Rejected making the safety sections always-on: that inflates every request with a 463-line rules file where routing is what keeps the cost down.

Adding content routing exposed a related latent bug upstream — HUMAN_ONLY_SECTIONS was never subtracted in the tenferro-rs copy, so its "intentionally not routed" guarantee held only because no trigger happened to name one. Fixed there too.

Also folded in

The tenferro-rs review raised a cumulative-retry problem that applies here identically: at a 300s per-attempt timeout a three-chunk diff could exceed the job timeout and be killed mid-request, losing the report. All LLM traffic is now bounded by a 900s budget, with a warn (not block) when it runs out, since the deterministic checks still cover the whole diff.

Added docs/worklogs/2026-08-04-review-bot-hardening.md — this repository's Work Logs And Design Records rule applies to this change, and it is in ALWAYS_SECTIONS, so the bot would flag its own PR for the omission.

Verification

80 script tests pass, actionlint clean, and the bot reviews its own branch cleanly. Fixture strings are assembled at runtime so the file no longer trips its own guard; self-scan reports zero hits.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f59bc3720e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/repository-rules-review.py Outdated
Comment on lines +814 to +816
for path, entries in added_lines_with_text(diff_text).items():
for line_no, text in entries:
if contains_sensitive_text(text):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block secrets added on a continuation line

Fresh evidence beyond the prior multiline fix is a declaration whose assignment line is unchanged, such as const API_KEY: &str =, while the following value line is replaced with a quoted credential. This loop checks each added line in isolation, so the value line has no credential-shaped name and is not blocked; the full-diff redactor then consumes the intervening deletion marker as the assignment value and leaves the new literal available for external upload. Inspect adjacent context for continued assignments or conservatively block standalone quoted values replacing such assignments.

AGENTS.md reference: AGENTS.md:L285-L290

Useful? React with 👍 / 👎.

Comment thread scripts/repository-rules-review.py Outdated
Comment on lines +1284 to +1285
if crate is None or crate == TENFERRO_ROUTE_CRATE:
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check non-crates workspace package manifests

When a workspace package outside crates/—for example docs/tutorial-code/Cargo.toml, tools/api-dump/Cargo.toml, or xtask/Cargo.toml—adds a direct runtime tenferro-* dependency, crate_of() returns None and this branch skips the manifest. The LLM prompt also suppresses this class of finding because the deterministic gate is expected to cover it, so the new dependency can pass entirely undetected; exempt only the tensorbackend package rather than every manifest outside crates/.

AGENTS.md reference: AGENTS.md:L290-L292

Useful? React with 👍 / 👎.

Comment on lines +1258 to +1260
renamed = TENFERRO_PACKAGE_VALUE.search(line)
if renamed:
found[line_no] = renamed.group(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore commented package assignments

When an added comment inside a dependency table contains an example such as # linalg = { package = "tenferro-linalg" }, this unanchored search classifies the comment as a real dependency and produces a block-severity finding. The same false positive occurs when package = "tenferro-*" appears in an inline comment after an unrelated dependency, so strip TOML comments or parse the manifest structurally before matching package aliases.

AGENTS.md reference: AGENTS.md:L290-L297

Useful? React with 👍 / 👎.

Continuation-line secrets: an assignment can stay unchanged on a context line
while only the value line is replaced, so checking added lines in isolation
sees a bare literal with no credential-shaped name. sensitive_diff_location
now walks the diff in order and tracks an assignment whose value has not
appeared yet. The redactor's separator is also line-local now; it used to
cross the newline and consume the deletion marker as the value, leaving the
new literal untouched.

Cargo manifests outside crates/ were skipped entirely because crate_of()
returned None. xtask, tools/api-dump, docs/tutorial-code, and docs/book-tests
are workspace members, so a direct tenferro dependency there passed
undetected. Scope is now every Cargo.toml, exempting the route package by its
[package] name rather than its directory.

TOML comments were treated as declarations, so a commented example or a
trailing comment produced a block finding. Comments are stripped, respecting
quoted hashes.

The new fixtures tripped the new detector, as with the earlier rounds; the
opener is assembled at runtime so the file no longer trips its own guard.

Refs #566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/tenferro-rs that referenced this pull request Aug 4, 2026
Ported from the third Codex review round on tensor4all/tensor4all-rs#568,
which reviewed this same shared script.

An assignment can stay unchanged on a context line while only the value line
is replaced, so checking added lines in isolation sees a bare literal with no
credential-shaped name and uploads it. sensitive_diff_location now walks the
diff in order and tracks an assignment whose value has not appeared yet.

The redactor's separator is line-local now. It used to cross the newline and
consume the deletion marker as the assignment value, leaving the following
line's literal untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@terasakisatoshi

Copy link
Copy Markdown
Member Author

Third round: all three findings were real and are fixed. Ported to tensor4all/strided-rs#224 and tensor4all/tenferro-rs#1603.

P1 — secrets on a continuation line

Confirmed, and it defeated both halves of the guard. The assignment stays on an unchanged context line while only the value line is replaced:

 const API_KEY: &str =
-    "old";
+    "s3cr3t-passphrase";

Detection saw a bare literal with no credential-shaped name. And the redactor's \s* separator crossed the newline, consumed the - deletion marker as the assignment value, and masked that — so the fallback silently did nothing either.

sensitive_diff_location now walks the diff in order rather than per file, tracking whether the previous surviving line opened an assignment whose value has not appeared yet. A deleted line neither survives nor breaks the continuation. The redactor's separator is line-local now. Negative coverage: let message = / "hello world there"; is not blocked, and an unchanged continuation value is not reported, since only added lines are in scope.

P1 — workspace packages outside crates/

Confirmed. The workspace has four such members:

"tools/api-dump", "xtask", "docs/book-tests", "docs/tutorial-code"

crate_of() returned None for all of them and the manifest was skipped, so a direct runtime tenferro-* dependency there passed undetected — and the prompt suppresses this class of finding on the assumption the deterministic gate covers it.

Scope is now every Cargo.toml. Your suggestion to exempt only the tensorbackend package is what I implemented, and keying the exemption on the [package] name rather than the directory turned out to matter: it caught an inconsistent test fixture of mine whose path said tensorbackend while its manifest declared tensor4all-core.

P2 — commented package assignments

Confirmed for both forms — a commented example line and a trailing comment after an unrelated dependency each produced a block finding. Comments are stripped before matching, respecting # inside quoted values (a = "x#y" # note).

Recurring pattern worth noting

For the third round running, the fixtures written to test a new detector tripped that detector. Each round it was caught only by running the bot against its own branch (--worktree), not by the test suite. The opener is now assembled at runtime like the other fixtures, and the self-scan is part of my verification loop.

Verification

89 script tests pass. Historical regression checks still behave: 795c087fail (dependency), 21c2f07fail (doctest), 69a24e7pass. Self-scan of both scripts reports zero hits and the bot reviews its own branch cleanly.

budget_exhausted_finding interpolated DEFAULT_BUDGET_SECONDS into its message
regardless of --budget-seconds, so `--budget-seconds 30` produced a diagnostic
claiming a 900s budget ran out. That misleads exactly the reader who is trying
to work out why a review came back incomplete. Pass the configured value
through and assert both halves in the test.

Refs #566

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50abb4db5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/repository-rules-review.py
Comment thread scripts/repository-rules-review.py
Comment thread scripts/repository-rules-review.py
@terasakisatoshi
terasakisatoshi merged commit 70b379d into main Aug 4, 2026
7 checks passed
@terasakisatoshi
terasakisatoshi deleted the add-repository-rules-review-bot branch August 4, 2026 03:15
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
Deletion-only violations were silently dropped. filter_findings discarded any
file-level block finding, but a violation introduced by deleting required
validation, coverage, or a safety comment has no new-file line to anchor to,
so the model must return line: null. Such a diff passed the gate even when the
LLM correctly identified a blocking violation. File-level blocks are now kept
for files this diff deletes lines from.

Continuation values need not be quoted. An unchanged `API_KEY =` followed by a
replaced bare value line was neither detected nor redacted, so the credential
was sent verbatim. STANDALONE_VALUE now accepts bare tokens; awaiting_value is
only set for credential-named assignments, so this cannot fire on ordinary
multi-line expressions.

A timeout at the cumulative deadline was reported as a block. The budget
clamps the last chunk to the remaining time, and that timeout was converted
into llm-review-unusable, failing the gate for exactly the case the budget
exists to degrade gracefully. Deadline-triggered transport failures now emit
the warn-only budget-exhausted finding.

Refs #199

Ported from the Codex review of tensor4all/tensor4all-rs#568.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@terasakisatoshi

Copy link
Copy Markdown
Member Author

Fourth round: all three findings were real and are fixed, along with the parallel finding on tensor4all/tenferro-rs#1603. Ported to tensor4all/strided-rs#224 as well.

P1 — deletion-only changes lost their block findings

Confirmed, and this one was a hole in the gate's premise. filter_findings dropped every unanchored block, but a violation introduced by deleting required validation, coverage, or a // SAFETY: comment has no new-file line to point at — the model is obliged to return line: null. So a deletion-only diff passed the advertised gate even when the LLM correctly identified a blocking violation.

The unanchored-block rule still earns its keep against the model generalising about a file, so rather than dropping it I scoped the exception: file-level blocks survive for files this diff deletes lines from. files_with_deleted_lines keys on the new-side path so it lines up with how findings are addressed.

P1 — unquoted continuation values

Confirmed. My previous round's STANDALONE_VALUE only accepted quoted literals, so:

 API_KEY =
-old
+abcdefghijklmnopqrstuvwx

went through untouched. Bare tokens are accepted now. The false-positive risk you'd normally worry about does not apply here: awaiting_value is only set when the opener's name passed is_credential_name, so an ordinary let total = / compute_sum(values) continuation cannot trigger it — asserted in a negative test.

P2 — [workspace.dependencies] falsely blocked

Confirmed, and it would have bitten immediately: a virtual root declares versions for members to opt into, so every routine tenferro revision bump in the root manifest would have been reported as a new direct dependency outside tensorbackend. dependency_table_of now rejects any table path rooted at workspace, while target.'cfg(...)'.dependencies keeps working.

From #1603 — deadline timeouts reported as blocks

Worth surfacing here since it is the same file. The budget clamps the final chunk's timeout to the remaining time; when that clamped request timed out, the handler turned it into llm-review-unusable (block) instead of the warn-only budget-exhausted finding. That failed the gate for precisely the case the budget exists to degrade gracefully — the mechanism defeated its own purpose. Deadline-triggered transport failures now emit the warn.

Verification

95 script tests pass, self-scan clean, and the bot reviews its own branch cleanly. Historical regressions unchanged: 795c087fail, 21c2f07fail, 69a24e7pass.

terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
Ported from the Codex review of tensor4all/tensor4all-rs#568, which reviewed
this same shared script.

Quoted secrets containing spaces were neither detected nor fully redacted, so
most of a passphrase was uploaded. Widening the value pattern needs the name to
carry the discrimination, since a diceware passphrase is prose by construction.
git C-quoted non-ASCII pathnames, so those files were reviewed by nothing.
Path-only routing never supplied the unsafe rules for an unsafe block added
under a generic filename, and the prompt forbids inventing unsupplied
requirements, so the rule was unenforceable there. Cumulative retries could
outlive the job timeout and lose the report entirely.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
Ported from the third Codex review round on tensor4all/tensor4all-rs#568.

An assignment can stay unchanged on a context line while only the value line
is replaced, so checking added lines in isolation sees a bare literal with no
credential-shaped name and uploads it. sensitive_diff_location now walks the
diff in order and tracks an assignment whose value has not appeared yet.

The redactor's separator is line-local now. It used to cross the newline and
consume the deletion marker as the assignment value, leaving the following
line's literal untouched.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
Deletion-only violations were silently dropped. filter_findings discarded any
file-level block finding, but a violation introduced by deleting required
validation, coverage, or a safety comment has no new-file line to anchor to,
so the model must return line: null. Such a diff passed the gate even when the
LLM correctly identified a blocking violation. File-level blocks are now kept
for files this diff deletes lines from.

Continuation values need not be quoted. An unchanged `API_KEY =` followed by a
replaced bare value line was neither detected nor redacted, so the credential
was sent verbatim. STANDALONE_VALUE now accepts bare tokens; awaiting_value is
only set for credential-named assignments, so this cannot fire on ordinary
multi-line expressions.

A timeout at the cumulative deadline was reported as a block. The budget
clamps the last chunk to the remaining time, and that timeout was converted
into llm-review-unusable, failing the gate for exactly the case the budget
exists to degrade gracefully. Deadline-triggered transport failures now emit
the warn-only budget-exhausted finding.

Refs #199

Ported from the Codex review of tensor4all/tensor4all-rs#568.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/strided-rs that referenced this pull request Aug 4, 2026
* Address the second Codex review round

Ported from the Codex review of tensor4all/tensor4all-rs#568, which reviewed
this same shared script.

Quoted secrets containing spaces were neither detected nor fully redacted, so
most of a passphrase was uploaded. Widening the value pattern needs the name to
carry the discrimination, since a diceware passphrase is prose by construction.
git C-quoted non-ASCII pathnames, so those files were reviewed by nothing.
Path-only routing never supplied the unsafe rules for an unsafe block added
under a generic filename, and the prompt forbids inventing unsupplied
requirements, so the rule was unenforceable there. Cumulative retries could
outlive the job timeout and lose the report entirely.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Block secrets added on a continuation line

Ported from the third Codex review round on tensor4all/tensor4all-rs#568.

An assignment can stay unchanged on a context line while only the value line
is replaced, so checking added lines in isolation sees a bare literal with no
credential-shaped name and uploads it. sensitive_diff_location now walks the
diff in order and tracks an assignment whose value has not appeared yet.

The redactor's separator is line-local now. It used to cross the newline and
consume the deletion marker as the assignment value, leaving the following
line's literal untouched.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Report the configured review budget, not the default

budget_exhausted_finding interpolated DEFAULT_BUDGET_SECONDS into its message
regardless of --budget-seconds, so `--budget-seconds 30` produced a diagnostic
claiming a 900s budget ran out. That misleads exactly the reader who is trying
to work out why a review came back incomplete. Pass the configured value
through and assert both halves in the test.

Refs #199

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix four more review-bot gaps

Deletion-only violations were silently dropped. filter_findings discarded any
file-level block finding, but a violation introduced by deleting required
validation, coverage, or a safety comment has no new-file line to anchor to,
so the model must return line: null. Such a diff passed the gate even when the
LLM correctly identified a blocking violation. File-level blocks are now kept
for files this diff deletes lines from.

Continuation values need not be quoted. An unchanged `API_KEY =` followed by a
replaced bare value line was neither detected nor redacted, so the credential
was sent verbatim. STANDALONE_VALUE now accepts bare tokens; awaiting_value is
only set for credential-named assignments, so this cannot fire on ordinary
multi-line expressions.

A timeout at the cumulative deadline was reported as a block. The budget
clamps the last chunk to the remaining time, and that timeout was converted
into llm-review-unusable, failing the gate for exactly the case the budget
exists to degrade gracefully. Deadline-triggered transport failures now emit
the warn-only budget-exhausted finding.

Refs #199

Ported from the Codex review of tensor4all/tensor4all-rs#568.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
terasakisatoshi added a commit to tensor4all/tenferro-rs that referenced this pull request Aug 4, 2026
Deletion-only violations were silently dropped. filter_findings discarded any
file-level block finding, but a violation introduced by deleting required
validation, coverage, or a safety comment has no new-file line to anchor to,
so the model must return line: null. Such a diff passed the gate even when the
LLM correctly identified a blocking violation. File-level blocks are now kept
for files this diff deletes lines from.

Continuation values need not be quoted. An unchanged `API_KEY =` followed by a
replaced bare value line was neither detected nor redacted, so the credential
was sent verbatim. STANDALONE_VALUE now accepts bare tokens; awaiting_value is
only set for credential-named assignments, so this cannot fire on ordinary
multi-line expressions.

A timeout at the cumulative deadline was reported as a block. The budget
clamps the last chunk to the remaining time, and that timeout was converted
into llm-review-unusable, failing the gate for exactly the case the budget
exists to degrade gracefully. Deadline-triggered transport failures now emit
the warn-only budget-exhausted finding.

Ported from the Codex review of tensor4all/tensor4all-rs#568.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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