Repository navigation
Add a weekly lane triage: the model proposes, a model-free job applies - #896
seathatflowsinourveins wants to merge 15 commits into
Conversation
|
Dependency limit exceeded — report not shown. This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report. Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard. Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account. |
claude-triage.yml runs weekly and on dispatch from main while CLAUDE_TRIAGE_ENABLED is 'true'. The classify job collects the open issues and pull requests without a lane label as data, and Claude (read-only fence, 6 turns, $1) answers through a JSON schema with an allow-listed lane per item. A model-free step checks every number against the collected items and keeps only high-confidence issue labels; the apply job, with no model and issues: write only, re-reads each issue and adds the label only to an open issue that still has none. Pull requests get suggestions in the summary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
096ad1a to
ca5ad56
Compare
The classify job now passes `--effort low` in claude_args. Under the command center's effort mapping of 2026-10-08, classification, routing and short probes run at `low`; this job's labels are reversible and the model-free apply job re-checks each one against the repository's label set. The level has to be in claude_args because `--restricted` ignores the settings files that would otherwise carry it, and Opus 5.5 runs at `medium` on the API when a request leaves effort unset. The bounds test now requires the flag (removing it fails the test). Co-Authored-By: Claude Opus 5.5 <[email protected]>
The bounds step failed the job unless the result's num_turns was at most the --max-turns value. On Claude Code 2.1.295 num_turns counts transcript messages, tool results included: a local run with #894's prompt and claude_args (api-actions LR, 2026-10-08) made 12 API requests under --max-turns 12 and reported num_turns 57. The step now counts distinct assistant message ids in the execution file and checks that count against 6; num_turns stays in the record and the job summary as "messages". The test fixture emits two messages per turn id, as the client does, and a new test passes a 6-turn run that reports 57 messages and fails a 7-turn one. Three mutants (bound on num_turns, count forced to 0, count without de-duplicating ids) each fail the tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
main moved to aba02ec (#889 and others), and manifests/evidence.json conflicted, so no pull_request workflow ran on the branch. The manifest is resolved by the hot-file protocol: main's copy, then this branch's 7 changed files re-registered with host_receipts.register_file, followed by component_matrix.py --write and new_host_grand_list.py --write; validate.py exits 0. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pre-cue read of the sibling harness-audit PR found gaps that this workflow shares; this commit carries the same fixes here. Bounds step: an allow-list tool check (Glob, Grep, Read and StructuredOutput), a session start record without its tool or MCP lists fails, a run without a structured output fails (this job answers only through --json-schema, so the answer bound is the structured output, not result text), and the step names every bound it finds unmet. Tests: the step tests read the workflow with the policy test's own loader when PyYAML is absent, so they no longer skip; new cases cover an unknown tool, a start record without lists, a missing structured output and the named failures (18 tests). Record: a section names every behaviour of the new workflow (triggers, slug, owner, first-attempt and variable guards, timeouts and grants per job, the guard exit, the collect limits, the explicit action inputs verified in the pinned action.yml, the usage artifact, the apply job); the evidence line says this workflow's exact claude_args have not run; the 2026-10-03 cooldown waiver is cited. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The sweep of the W4 drafts for the classes #894's pre-cue toolkit read found, applied here: when there were items to classify, a classify step that succeeds without an execution file now fails the job (with no items the step is skipped by design); show_full_output, display_report and track_progress are asserted; the record states that a public run's summary is public; ci-least-privilege lists both triage jobs in its tables and names each exempt job (in place). The workflow has no paths input, so the bash 3.2 empty-array class does not apply. 19 tests; actionlint and zizmor report nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pre-cue toolkit read of 53379c7 left three findings at confidence 80 or above that 55da753 did not touch: - The guard step had no test. It now has one: each debug signal, a settings file, and a dangling link to one. - `apply` reported an issue it could not read as skipped and exited 0. It now records a read or label error in the summary, tries the remaining issues, and fails the step. - Test gaps: the tests now assert the numbers step's `always()`, the validation step's `success()`, the usage artifact, an item the model leaves out, and each numbers-step failure by its message. The shape tests no longer skip without PyYAML. The record's test count was stale at 18; it is now 25. Ten weakened copies of the workflow each fail at least one test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The J8 micro read of 5d9b35d found the three earlier findings fixed and two smaller gaps: - A body jq could not check still fell to "skipped". That covers a body that is not JSON, an empty body, and a runtime error such as labels not being a list. The record's "never reported as skipped" was therefore wider than the code. `apply` now reads jq's exit status: only 1 (a false check) is a skip, and any other exit is a failure that names jq's exit status. Measured on jq 1.8.1: 5 for a body that is not JSON or a runtime error, 4 for an empty body. - The label-failure test proposed a single issue, so it could not show that the others are still tried. It now proposes a failing label first and a good one second. A new test covers the three unusable bodies followed by an issue that is still labelled. Five more weakened copies each fail at least one test, and the earlier ten still do. The test count is now 26. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ge guard The cc-native-practice lane's L3 read against claude-code-action v1.0.247 practice (requirement R4) found that the guard step could not see debug logging enabled through a repository secret or variable. Step debug logging and runner diagnostic logging are each enabled by a secret or a variable named ACTIONS_STEP_DEBUG or ACTIONS_RUNNER_DEBUG, the secret taking precedence. Neither reaches a step's shell unbound, and runner.debug does not reflect runner diagnostic logging. - The guard now binds (secrets.X || vars.X) == 'true' for both, so only true or false reaches the shell, and refuses either before any token is requested. - A test pins both bindings, and the guard test refuses each setting. - Six weakened copies each fail at least one test. This is hardening: the action step already pins ACTIONS_STEP_DEBUG 'false'. zizmor's auditor persona reports secrets-outside-env on the two reads; CI runs the regular persona. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…nd raise the caps to 8 turns, $2 and high effort R5, quality and correctness only. claude_args keeps --setting-sources user beside --restricted: runs of the installed Claude Code 2.1.295 measured that --restricted keeps the project, local and user settings files out under any source list, that --setting-sources user alone keeps the project and local files out, and that a --settings flag is still honoured. The caps follow the command center's derivation: 8 turns, $2 and effort high, to be replaced by the first measured runs. The numbers step bounds 1 to 8 assistant turns and $2, requires the client version the pin installs (2.1.295, read at the action's pin), and counts every tool-list entry that is not a string as a forbidden tool. The apply step refuses an empty proposal, builds and counts its rows before the loop, and keeps both gh calls off the loop's stdin. New tests pin claude_args and --settings exactly; the module goes from 27 to 35 tests. The record gains a dated R5 section with the settings-source measurements. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…get stops, and drop --max-turns R6. The W4 series sets each cost bound at the budget times 1.10; triage is the stated exception and keeps $2: with --json-schema the pinned action returns no structured proposal from a budget stop, so there is nothing to apply or publish. The usage record keeps result_subtype and the failure names the stop. --max-turns 8 leaves claude_args: the pinned action fails a success whose num_turns exceeds it, and on Claude Code 2.1.295 num_turns counts transcript messages (57 for 12 requests); the numbers step's check of 1 to 8 assistant turns stays. Both exact pins compare canonical JSON. The record gains the shared cost paragraph, the exception's reason and an upstream-issue draft (not filed). Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command center's ruling on the GPT read of 5c74982: the draft's repro now states the predicate exactly, "a successful run whose reported num_turns (a message count) exceeds --max-turns", instead of a sample invocation. Record only; the manifest row is rehashed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…894) ### Scope - What this PR changes, in one or two sentences: adds `claude-pr-review.yml`, an on-demand, read-only review of one same-repository pull request head by `anthropics/claude-code-action` v1.0.247. A maintainer dispatches it from `main` with the pull request number and the exact head commit; the review goes to the job summary and nothing is posted to the pull request. It runs only while the repository variable `CLAUDE_PR_REVIEW_ENABLED` is `true`, so merging this starts nothing. - Effort (added 2026-10-08 at head a6d2379): `--effort max` in `claude_args`, because judgment work (designated reads, adjudication, pull request and security reviews, audits) runs at `max` under the command center's effort mapping, and every job records its level and reason. The level has to be in `claude_args`: `--restricted` ignores the settings files that would otherwise carry it, and Opus 5.5 runs at `medium` on the API when a request leaves effort unset. The bounds test now requires the flag; removing it fails the test. A loopback dry run of the installed 2.1.295 client with this workflow's `claude_args` sent `output_config.effort: "max"` on every request. - Fixes from the pre-cue read of the sibling #892 (2026-10-09, head d6f8909): the record names every behaviour of this new workflow: the trigger; the slug, owner, first-attempt and variable guards; the timeout and grants; the guard and binding exits; the head-as-data checkout and diff limits; the explicit action inputs `display_report` and `track_progress` (defaults in the pinned `action.yml`); and the 14-day usage artifact. The bounds step now uses an allow-list, fails on a start record without lists or on an empty result, and names each unmet bound. The review cut is character-safe and says when it cut. The step tests no longer skip without PyYAML. The 2026-10-03 cooldown waiver is cited. - Fixes from this PR's own pre-cue toolkit read (J8 on d6f8909, head 8e64120): - the empty paths array is now expanded safely on bash before 4.4; - a success without an execution file now fails the job; - `display_report` and `track_progress` are asserted; - the artifact name and the action-pin sentence are corrected, and the parity receipt's dependency on #892 is stated; - `claude-pr-review.yml:review` is now in both grant tables of ci-least-privilege; - the record states that a public run's summary is public. - Pre-cue toolkit read of this PR (J8 on d6f8909, 2026-10-09; `pr-test-analyzer` and `silent-failure-hunter`): every finding at confidence 80 or above, and its status at head 41c9c15. | Finding | Status at 41c9c15 | Fixing commit and evidence | | --- | --- | --- | | `pr-test-analyzer` 1 (90): the record names the wrong artifact | fixed | 8e64120: the record names `claude-pr-review-usage-<run id>-<attempt>`, the upload's name; 41c9c15 adds `test_the_usage_artifact_is_named_for_the_run_and_its_attempt` | | `silent-failure-hunter` 2 (88): the record and the workflow disagree on the artifact | fixed | the same two commits | | `pr-test-analyzer` 2 (85): `docs/github-automation.md` claims the harness audit's action pin | fixed | 8e64120: the bullet says the two pins differ until #892 lands | | `silent-failure-hunter` 1 (92): the same false claim (pin and federation variables) | fixed | 8e64120, as above; the four federation variables are the same | | `pr-test-analyzer` 3 (85): the headline evidence file is not in this PR | fixed | 8e64120 states that #892 adds it and lands first; 41c9c15 cites its `LR` entry field by field | | `silent-failure-hunter` 3 (80): "#897" and "two blocking findings" have no source in that receipt | fixed | #892 adds `target_pull_request`, `target_head`, `report_blocking_findings` and `report_sha256` to the `LR` entry (a marked 2026-10-09 amendment from the run's harness receipt); 41c9c15 cites them, calls the verdict the model's own, and states that the prompt and `claude_args` are unchanged from the run's workflow commit a6d2379 to that head (R5 later raises the two caps and the prompt's turn sentence; see R5 below) | - R4, debug logging set as a repository secret or variable (2026-10-09; the cc-native-practice lane's L3 read against `anthropics/claude-code-action` v1.0.247 practice): the guard binds `(secrets.ACTIONS_STEP_DEBUG || vars.ACTIONS_STEP_DEBUG) == 'true'` and `(secrets.ACTIONS_RUNNER_DEBUG || vars.ACTIONS_RUNNER_DEBUG) == 'true'` as booleans and refuses either before any token is requested; a test pins both bindings, and six weakened copies each fail at least one test. This is hardening: the action step already pins `ACTIONS_STEP_DEBUG: 'false'`, which is what the action's full-output switch reads. zizmor's auditor persona reports `secrets-outside-env` on the two reads; CI runs the regular persona with `--no-ignores`, which reports nothing. The R4 lines are byte-identical in #892, #894, #895, #896 and #909; R5 leaves them unchanged here. - The report notice at the cap (2026-10-09; the J8 micro read of #892): the publish step wrote with `jq -r`, which adds a newline, so a 60,000-byte report was announced as cut. It writes with `jq -j` now; a test publishes exactly 60,000 and 60,001 bytes, and a copy that writes with `jq -r` fails it. R5 adds the 59,999-byte case. - From the J8 micro read of 41c9c15 (2026-10-09; 4 of 6 listed findings fixed, PTA-3 and SFH-3 partly because the receipt the record cites was not in this tree): this PR now carries a byte-identical copy of `local-parity-receipt.json` (sha256 `ba498b82…`, the file #892 adds), so the citation no longer depends on which PR lands first, and the docs bullet states the action pin and the federation variables instead of comparing with the harness audit on `main`. - R5 (2026-10-09, head d1bcb6c, limited by the command center to correctness and test quality) adds a model-free step, `Remove symbolic links from the pull request head`, right after the head checkout: `set -euo pipefail; find pr-head -path pr-head/.git -prune -o -type l -exec rm -f {} +`. It raises the approved caps from measurement: `--max-turns` 12→30 and `--max-budget-usd` 3→5. The prompt's "You have at most 30 turns", the numbers step's bounds (1 to 30 assistant turns, at most $5, with matching messages), the header comment and `docs/github-automation.md` follow. The numbers step now counts each non-string tool-list entry as a forbidden tool named `non-string tool entry` instead of dropping it. The R4 guard lines, the deny list and the `github_token` input are kept exactly as at a1bc3bc. The test module goes from 33 to 41 tests. Eight are new: the symlink step's position and exact text; its behaviour on planted links to `/proc/self/environ`, `../.git/config` and a subdirectory, plus a re-run on a link-free head; a 59,999-byte report published whole; object, null and number tool entries refused and named; the prompt stating the `--max-turns` value; and the caps at 30 turns and $5 and just above. Changed tests pin the full `claude_args` list and `--settings` JSON exactly, assert the exact `if:` strings of the publish and numbers steps, run the guard with production's debug-off values (false/false/empty), and check that the 59,999-byte prefix before a cut character survives. All 28 weakened copies of the workflow fail at least one test with PyYAML present, as the hosted validate job has it; without PyYAML 15 of 28 do, because the pins sit in the PyYAML-only shape tests. The decision record gains a dated R5 section and names the R4 guard change in its what-changed paragraph. It corrects "Read denies for every `.git` directory" to "for the files under every `.git` directory". It restates the cap derivation and the expected cost per run, and withdraws the dry $0.70–$1.30 estimate, which was below the measured first-read cost. It adds the shared follow-up line, the shared `origin` URL sentence and two action sources. `manifests/evidence.json` is rehashed for the four changed files. - Sources of the R5 items: the command center's security read of a1bc3bc (the symbolic links, the closed flag list, the exact `if:` strings, the guard's debug-off values, the 59,999-byte prefix, and the stale guard sentence in the record); the command center's caps decision of 2026-10-09; the GPT designated read of #895, P2 (non-string tool entries, applied to all five W4 workflows); the cc-native-practice lane's third audit, P3-1 (the shared `origin` URL sentence). - Caps derivation: `LR`, a first read of #897 with this workflow's prompt and `claude_args` on the installed client, used all 12 of its 12 assistant turns and cost $2.23 ($1.87 by the client's estimate). So the need is above 12, and 2.5 times that floor gives 30 turns; twice $2.23 is about $4.5, rounded up to $5. Four delta re-reads (LR2 to LR5) used 5, 6, 7 and 6 turns and cost $1.75, $1.35, $1.54 and $0.73. A run is expected to cost about $1.9 to $2.3 for a first read and $0.7 to $1.8 for a delta re-read. - Filed as follow-ups before any enabling variable is set, not in R5: the security hardening from the 2026-10-09 read (debug-value widening, the tool-list shape apart from the non-string entry above, extra deny rules, token-source and guard-step assertions); the rest of F-02 (a non-empty tool list, checking the `tool_use` names); F-16 (the pins that run only with PyYAML). - Not run in R5: a fence smoke with a planted symbolic link, which needs a model run; any hosted run. On the first run, watch whether the action appends an `--mcp-config` that `--strict-mcp-config` still honours (the read's G3); the numbers step fails the run on any MCP server. - Base commit: `aba02ec3` (merge base at a1bc3bc and at d1bcb6c, which builds on a1bc3bc; as read at a1bc3bc, main had moved to `ebe6c223` with #890, #900, #897 and #907; only `manifests/evidence.json` overlaps, and the one rebase happens at landing) - Lane: `lane:foundation` - Owned paths touched: `.github/workflows/claude-pr-review.yml` (new), `tests/test_claude_pr_review_workflow.py` (new), `tests/test_workflow_policy.py` (one inventory entry, one exemption), `tests/test_workflow_security_coverage.py` (coverage set and one zizmor test), `docs/decisions/2026-10-08-claude-actions-pr-review.md` (new), `docs/decisions/2026-10-04-ci-least-privilege.md` (dated addendum only), `docs/github-automation.md` (current-practice bullet), `evidence/artifacts/claude-actions-fence-smoke-20261008/receipt.json` (new), `evidence/artifacts/claude-actions-fence-smoke-20261008/local-parity-receipt.json` (new; byte-identical to the copy #892 adds), `manifests/evidence.json` (registration, last commit). ### R6 (2026-10-09), at bd10ba3 - **Cost bound:** the budget × 1.10 = $5.50, from four measured J8 runs on a $5 budget ($5.46, $5.33, $5.16, $5.0007), re-derived after the first three hosted runs. - A budget stop at or under it publishes the text written before the stop, with a line naming the stop. - Publishing is gated on `!cancelled() && steps.numbers.outcome == 'success'`, because the action fails its own step on any budget stop. - **`--max-turns 30` removed:** the pinned action (`base-action/src/run-claude-sdk.ts:241-250` at 2dca132f, since 6ef6450f) fails a success whose `num_turns` exceeds it. - On Claude Code 2.1.295, `num_turns` counts transcript messages: 57 for 12 requests under `--max-turns 12`. That is a code reading plus a local measurement, not a hosted run. - The numbers step's 30-assistant-turn bound and the prompt's "at most 30 turns" stay. - **Job timeout:** 20 → 30 minutes. 30 turns at the measured pace (12 turns in 512,670 ms) take about 21.4 minutes before setup. #892 uses 30. - **Settings pins:** both compare canonical JSON, so `1` no longer passes for `true` (J8 R5 micro, N1, confidence 70). - **Record:** - the shared cost paragraph; - an upstream-issue draft (not filed); - the seven changed test expectations the J8 R5 micro found undeclared (U1–U7). - **Local checks at bd10ba3:** - `validate.py` and the manifest check pass (10,906 files); - six modules: 262 tests, OK; - this module: 44 tests (16 skip without PyYAML); - actionlint and zizmor are clean; - 39 of 39 weakened copies fail a test with PyYAML. - **Hosted checks at bd10ba3:** not read yet. ### SOTA sources - anthropics/claude-code-action, v1.0.247 at `2dca132ff0e0c4094ce6048b422c6915a071210b` (<https://github.com/anthropics/claude-code-action/tree/2dca132ff0e0c4094ce6048b422c6915a071210b>): `docs/security.md` ("Do not check out an untrusted ref into the workspace root before this action"; base ref at the root and head ref in a subdirectory), `docs/setup.md` (federation inputs; "a static credential takes precedence and federation will not be used"), `action.yml` (inputs and the `execution_file` output), `base-action/src/parse-sdk-options.ts` (argument parsing with `shell-quote`, default setting sources, debug forcing full output), `src/github/operations/git-config.ts` (git authentication in the root checkout; lines 129-134 write the job token into the checkout's `origin` URL when `allowed_non_write_users` is unset) and, new in R5, `src/modes/agent/index.ts` (lines 52-61, where agent mode calls that configuration). R5 read both files at v1.0.246 (`38c80c1`); the command center's compare of v1.0.246 and v1.0.247 changes only the bundled Claude Code version. - Claude Code 2.1.295, the version the pin installs, `claude --help`: `--restricted` ("removes the built-in tools that run commands or code ... and ignores user, project and local settings files ... Also confines the file tools to the working directories"), `--permission-prompts none` ("anything that would prompt is denied automatically"), `--tools`, `--settings`, `--strict-mcp-config`, `--max-budget-usd` and, for the R5 caps, `--max-turns`. The help does not say whether a symbolic link's target is resolved, which is why R5 removes the links before the model runs. - GNU findutils `find` 4.10.0 as installed (`find --version`), new in R5: `find pr-head -path pr-head/.git -prune -o -type l -exec rm -f {} +`, measured by the step test on planted links and on a link-free head (exit 0, no `rm` started). The runner image's `find` was not measured. - GitHub, Contexts reference, `runner.debug` (set only while debug logging is on, so the guard's `RUNNER_DEBUG_SIGNAL` is empty on a normal run), new in R5 as the basis of the guard tests' debug-off values; taken from the command center's read of a1bc3bc, not re-read in R5 (no network). - GitHub, OpenID Connect reference, "Filtering for pull_request events" and "Filtering for a specific branch" (<https://docs.github.com/en/actions/reference/security/oidc>); GitHub Security Lab, Preventing pwn requests (<https://securitylab.github.com/research/github-actions-preventing-pwn-requests/>). - Anthropic documentation read 2026-10-08: Workload identity federation (<https://platform.claude.com/docs/en/manage-claude/workload-identity-federation>); How Claude Code uses prompt caching (<https://code.claude.com/docs/en/prompt-caching>); Pricing (<https://platform.claude.com/docs/en/about-claude/pricing>). ### Evidence-class table | Claim | Evidence class | Command / receipt | | --- | --- | --- | | With the fence flags (`--restricted`, `--tools`/`--allowedTools` Read,Glob,Grep, `--strict-mcp-config`, `--permission-prompts none`, `--settings` with hooks off, `claudeMdExcludes` and three deny rules; Haiku 5.5, 10 turns, $0.50, as the receipt lists; this workflow adds `--setting-sources user`, `--add-dir`, `--effort max`, four deny rules and `blockReadsOutsideWorkingDirectories`, a key in the 2.1.295 build that the receipt does not exercise) the session's tools are Glob, Grep and Read; reads outside the tree and of `.git/config` are denied; planted hooks, instruction files and a skill under `pr-head` have no effect | `native_proven` on the installed 2.1.295 client (not through the action, not on a GitHub runner) | `evidence/artifacts/claude-actions-fence-smoke-20261008/receipt.json`, three runs | | The action's own parser turns this workflow's `claude_args` into those flags and that JSON | `local_integration` (at a1bc3bc only) | the parser's three helpers replayed with `shell-quote` 1.10.0 on the workflow text at a1bc3bc; not re-run at d1bcb6c (no network to install `shell-quote`). R5 changes two plain values (`12` to `30`, `3` to `5`), and `test_claude_has_three_read_tools_and_fixed_bounds` asserts the whole list, split per line with `shlex`, and the `--settings` JSON | | The guard, binding, symbolic-link, diff, numbers and review steps behave as described | `synthetic` | `tests/test_claude_pr_review_workflow.py`, 41 tests at d1bcb6c (the 26 step tests run without PyYAML too; the 15 shape tests need it); each step's shell is taken from the workflow file and executed against a local stand-in for `gh`, a local git repository or a planted `pr-head` tree | | Symbolic links under `pr-head` are removed before the model runs, `pr-head/.git` is left as the checkout wrote it, and a link-free head passes | `synthetic` | `test_symbolic_links_in_the_head_are_removed_and_nothing_else` (links to `/proc/self/environ`, `../.git/config` and `../..`; GNU find 4.10.0); no fence smoke with a planted link has run on the client | | The tests detect a weakened workflow | `synthetic` | 20 mutated copies on 2026-10-08 (that day's bounds), 20 failed at least one test; on 2026-10-09 three turn-bound mutants each failed, and the new bounds have their own tests; at 41c9c15 three artifact-name mutants each pass the 30 earlier tests and fail the new name test; at d1bcb6c 28 mutated copies (the symbolic-link step, the flag and settings pins, the `if:` strings, the guard's debug-off values, the report cap, the non-string tool entries, the caps and the prompt) each fail at least one test with PyYAML 6.0.3 present, as the hosted validate job has it; without PyYAML 15 of 28 do (the other 13 are pins in the shape tests; follow-up F-16) | | This workflow's own prompt and `claude_args` review a real pull request within bounds at `--effort max` on Opus 5.5 | `local_integration` (installed client, billed to a second key; not through the action) | `evidence/artifacts/claude-actions-fence-smoke-20261008/local-parity-receipt.json`, carried byte-identical in this PR and in #892 (sha256 `ba498b82…`), its `LR` entry: workflow commit a6d2379, whose prompt and `claude_args` are unchanged at d1bcb6c apart from the two caps R5 raised (`--max-turns` 12 to 30, `--max-budget-usd` 3 to 5) and the prompt's turn sentence; #897 at `target_head` 24821c5; 12 of 12 assistant turns; $1.868667 of the then $3 budget; tools Glob/Grep/Read; no MCP; `success`; `report_blocking_findings: 2`, the report's own verdict line, unchecked (`report_sha256` c49bee04, a 2026-10-09 amendment) | | The caps, 30 assistant turns and $5, bound a runaway at about twice the measured need; a run is expected to cost about $1.9 to $2.3 for a first read and $0.7 to $1.8 for a delta re-read | `local_integration` (installed 2.1.295 client, billed to the second key; not through the action) | the `LR` entry above (12 of 12 turns, client estimate $1.868667); the $2.23 figure and the delta re-reads LR2 to LR5 (5, 6, 7 and 6 turns; $1.75, $1.35, $1.54 and $0.73) come from the command center's caps decision of 2026-10-09 and are not in this repository | | In agent mode the action writes the job token into the root checkout's `origin` URL, so `persist-credentials: false` does not keep it out of `.git/config`; the deny rules keep the model from reading it | `source_review` for the code path; the denial of `.git/config` reads is the fence receipt's (first row) | `src/github/operations/git-config.ts:129-134` and `src/modes/agent/index.ts:52-61`, read at v1.0.246 (`38c80c1`; the command center's compare shows only the bundled client version changing at v1.0.247); the record's shared sentence also cites two 2026-10-09 probes on the installed 2.1.295, not reproduced here | | The workflow is well formed and has no audit finding | `local_integration` | at d1bcb6c: `actionlint` 1.17.0 exit 0; `zizmor` 1.30.1 offline, regular (`--no-ignores`) and pedantic, no findings (2 suppressed in each: `secrets-outside-env` on the R4 secret reads, as at a1bc3bc) | | A hosted run authenticates, stays in bounds and reads the cache | not established | no hosted run is part of this PR | ### Local commands run ``` $ python3 scripts/validate.py # on the R5 tree, committed as d1bcb6c {"components": 70, "hashed_files": 10906, "profiles": 4, "receipts": 239, "status": "passed"} exit 0 $ python3 scripts/evidence_manifest.py --check {"files": 10906, "status": "passed"} exit 0 $ uv run --quiet --no-project --python 3.12 --with pyyaml==6.0.3 python -m unittest tests.test_claude_pr_review_workflow tests.test_workflow_policy tests.test_workflow_security_coverage tests.test_workflow_hardening tests.test_github_automation_practice tests.test_adoption_docs_consistency Ran 259 tests ... OK (skipped=2) exit 0 $ uv run --quiet --no-project --python 3.12 python -m unittest tests.test_claude_pr_review_workflow # without PyYAML Ran 41 tests ... OK (skipped=15) exit 0 $ actionlint .github/workflows/claude-pr-review.yml exit 0 $ zizmor --offline --no-config --persona=pedantic .github/workflows/claude-pr-review.yml No findings to report. Good job! (2 suppressed) exit 0 $ zizmor --offline --no-config --no-ignores --persona regular .github/workflows/claude-pr-review.yml No findings to report. Good job! (2 suppressed) exit 0 $ uv run --quiet --no-project --python 3.12 --with pyyaml==6.0.3 python -B <scratchpad>/r5_pr894_mutants.py . a1bc3bc # outside the repository 28 mutants, 28 caught, survivors: [] exit 0 $ uv run --quiet --no-project --python 3.12 python -B <scratchpad>/r5_pr894_mutants.py . a1bc3bc # without PyYAML 28 mutants, 15 caught exit 1 (expected: the 13 survivors are pins in the PyYAML-only shape tests; follow-up F-16) ``` Hosted checks at d1bcb6c: not read yet. ### Decision record `docs/decisions/2026-10-08-claude-actions-pr-review.md` (why a manual dispatch and no pull request trigger, how the head is handled, the model's fence and what was checked at runtime, cost of one run with the measured runs and the cap derivation, changed test contracts, alternatives, what would overturn it), with a dated R5 section: the symbolic-link step, the caps from measurement, the non-string tool entries, the closed flag list and the boundary tests, the mutants, and the follow-ups. `docs/decisions/2026-10-04-ci-least-privilege.md` gets a dated addendum naming the second job on the federation rule. ### Host evidence Not applicable: no file under `evidence/hosts/` changes. ### Checklist - [x] New/changed GitHub Actions are pinned to a full commit SHA with a version comment (no floating tags). - [x] New/changed workflows declare top-level `permissions: {}` and grant each job only what it needs (`contents: read`, or an explicitly justified addition). - [x] No secrets are printed, logged or committed; no new required secret was added without a documented owner. - [x] No new paid hosting, subscription or billing surface was introduced. - [x] Peer-owned untracked files and worktrees were preserved (not deleted, moved or overwritten). Activation is a separate step after landing: an owner sets `CLAUDE_PR_REVIEW_ENABLED` to `true`, then dispatches the workflow with a pull request number and head commit. Runs authenticate by workload identity federation and spend from the Console organization the four `ANTHROPIC_*` repository variables name; no Anthropic key is stored in GitHub. Each run is capped at 30 assistant turns and a $5 client budget, the owner's per-run spend limit on that organization. This PR and #892 both edit the write-grant inventory and the evidence manifest, so the second to land rebases once. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…summary, is_error naming The record no longer says the run is capped at 8 turns (there is no client turn limit since R6; the numbers step bounds assistant turns after the run). The job summary names a budget stop. A success subtype whose is_error is not false is named in the failure message and kept in the usage record. The --max-turns check reads the raw claude_args text, and the $2 budget-stop case asserts no overrun message. Findings 4 and 6 are dispositioned in the record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t the N4 disposition The upstream-issue draft no longer says every normal run under --max-turns throws; it states the predicate (a successful, non-error result whose num_turns exceeds maxTurns). The N4 disposition no longer calls the 15,000-token estimate a measurement and states the timeout case plainly; the numbers step's comment qualifies its cost claim. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Parked: Claude review inside GitHub is retired (owner decision 2026-10-10, ~17:15Z); see #981 and docs/decisions/2026-10-10-retire-in-github-claude-review.md. No further pushes. |
|
Closing as superseded, with the owner's OK (CC, 2026-10-11). It adds .github/workflows/claude-triage.yml, which runs the Claude action. main's tests.test_workflow_hardening.InGitHubClaudeReviewRetiredTests now forbids any workflow that runs it. Claude review inside GitHub was retired by #981 (8bffa2c, docs/decisions/2026-10-10-retire-in-github-claude-review.md). The Anthropic API refuses the action's entry point on Console keys (CC correction #141; the action never had a successful run). AI reviews now run locally, and GitHub enforces them as required checks. The local route is #953 (the review worker). Nothing is lost: this PR can be reopened, and any model-free part of it can come back as a new PR under the local-review route. Evidence: the CC's PR triage (research-cc-role-20261008/pr-triage-20261011.md, class CLOSE). |
Scope
What this PR changes, in one or two sentences: adds
claude-triage.yml, a weekly and on-demand lane triage of the open issues and pull requests that carry no lane label. A model job with no write scope proposes labels through a JSON schema; a model-free job withissues: writere-checks each issue and adds an allow-listed lane label only to an open issue that still has none; pull requests get suggestions only. It runs only whileCLAUDE_TRIAGE_ENABLEDistrue, so merging this starts nothing.Effort:
--effort highinclaude_argssince R5 (2026-10-09). From head c956398 (2026-10-08) until then it waslow, the level the command center's effort mapping gives classification, routing and short probes; it returns tolowonly if a parity check on triage items shows thatlowholds. The labels are reversible: the model-free apply job re-checks each one against a fixed allow-list of three labels (lane:foundation,lane:trading,lane:shared). The level has to be inclaude_args, because--restrictedignores the settings files that would otherwise carry it; the exact-pin test requires it.Fixes from the pre-cue read of the sibling Bound the harness audit and move it to claude-code-action v1.0.247 #892 (2026-10-09, head 53379c7): the record names every behaviour of this new workflow. That covers the triggers; the slug, owner, first-attempt and variable guards; the timeouts and grants of each job; the guard exit and collect limits; the explicit action inputs
display_reportandtrack_progress(defaults in the pinnedaction.yml); the 14-day usage artifact; and the apply job. The bounds step now uses an allow-list (Glob, Grep, Read and StructuredOutput), fails on a start record without lists or without a structured output, and names each unmet bound. The step tests no longer skip without PyYAML. The 2026-10-03 cooldown waiver is cited.Pre-cue toolkit read of this PR (J8 on 53379c7, 2026-10-09; Anthropic's pr-review-toolkit
pr-test-analyzerandsilent-failure-hunter): every finding at confidence 80 or above, and its status at head 5d9b35d. 55da753 had touched none of the three.pr-test-analyzer(85): the guard step has no test, andGUARDis referenced nowheretest_the_guard_refuses_debug_logging_and_a_pre_existing_settings_fileruns the step with each debug signal, a settings file and a dangling link to onesilent-failure-hunter(88): inapply, an issue that cannot be read takes the else branch, is reported as skipped, and the step exits 0test_an_issue_that_cannot_be_read_fails_the_step_after_the_others_are_tried,test_a_label_that_cannot_be_added_fails_the_step_after_the_others_are_tried(renamed at e1ba3bb)silent-failure-hunter(60-80, counted at 80): test gapstest_an_item_the_model_leaves_out_gets_no_label, recorded as deliberate); the numbers step'salways()and the validation step'ssuccess(); the usage artifact; each numbers-step failure asserted by its message, the three unasserted ones included; the shape tests no longer skip without PyYAML (the fallback parser reads every condition they check); the stray blank line removedTen weakened copies of the workflow each fail at least one test (listed in the record).
Changes at 55da753 that this description had not named until then (the record names them): a final classify step, "Require the run's execution file", fails the job when items were collected and the action step succeeded without an execution file (before, that case was green), with
test_a_green_run_with_items_always_has_an_execution_filepinning its condition;test_the_action_is_pinned_federated_and_fencednow also assertsshow_full_output,display_reportandtrack_progressas'false';ci-least-privilege.mdis edited in place (two write-grant rows, one per-workflow row, and the federation-exemption sentence now naming each exempt job) besides its addendum; and the record gains a Visibility section (on this public repository every run's summary, proposed labels and reasons are public).The J8 micro read of the fix (53379c7..5d9b35d, both agents) found all three listed findings fixed, with no new finding at confidence 80 or above. Its two smaller findings are fixed at ace2512 (the command center ruled both fixed, quality first):
applyreadsjq's exit status and treats only 1 (a false check) as a skip, so a bodyjqcannot check is a failure that names the exit (5 or 4 on jq 1.8.1), never a skip; and the label-failure test now proposes a second issue that is still labelled. Five more weakened copies each fail at least one test.R4, debug logging set as a repository secret or variable (2026-10-09; the cc-native-practice lane's L3 read against
anthropics/claude-code-actionv1.0.247 practice): the guard binds(secrets.ACTIONS_STEP_DEBUG || vars.ACTIONS_STEP_DEBUG) == 'true'and(secrets.ACTIONS_RUNNER_DEBUG || vars.ACTIONS_RUNNER_DEBUG) == 'true'as booleans and refuses either before any token is requested; a test pins both bindings, and six weakened copies each fail at least one test. This is hardening: the action step already pinsACTIONS_STEP_DEBUG: 'false', which is what the action's full-output switch reads. zizmor's auditor persona reportssecrets-outside-envon the two reads; CI runs the regular persona with--no-ignores, which reports nothing. The R4 lines are byte-identical in Bound the harness audit and move it to claude-code-action v1.0.247 #892, Add an on-demand, read-only pull request review by the Claude action #894, Add an on-demand Claude security review and a model-free flag on risky paths #895, Add a weekly lane triage: the model proposes, a model-free job applies #896 and Add a pre-cue read by Anthropic's pr-review-toolkit agents in Actions #909.The J8 micro read of 5d9b35d..e1ba3bb (micro2, both agents, 2026-10-09) found the three listed findings fixed and no finding at confidence 80 or above. R5 fixes the residuals it raised: the guard description, per-source tests of the R4 bindings, the stale "read or labelled" wording, the R4 section's pointer to weakened copies, and checks re-run at the new head. Its lower-confidence notes (one refusal message for every debug signal, a valid-JSON body that is not an issue, jq exit numbers per version, a test loop without
subTest) are not taken in R5.R5, quality and correctness (2026-10-09, head 710d061). The command center narrowed this round to quality and correctness; security hardening from the 2026-10-09 read (debug-value widening, tool-list shape, extra deny rules, token-source and guard-step assertions) is filed as follow-ups before any enabling variable is set.
R5 (2026-10-09, quality and correctness only, on e1ba3bb):
claude_argskeeps--setting-sources userbeside--restricted; measured below, the two do not conflict, and each backs the other up. It raises--effort lowto--effort high(it returns tolowonly if a parity check on triage items showslowholds) and applies the command center's caps of 2026-10-09:--max-turns6 → 8 and--max-budget-usd1 → 2. Triage has no measured run, so the caps are an estimate: 30 items of up to about 2,200 characters are about 27,000 input tokens, and up to about 15,000 output tokens are assumed at high effort, about $0.41 a run at Claude Opus 5.5's standard prices. That is doubled, with a margin for high-effort thinking, to $2, and one read and one answer is a typical run, so 8 turns bound a runaway; both caps are to be replaced by about twice the p95 of the first measured runs. The numbers step now bounds 1 to 8 assistant turns and $2. It requires Claude Code 2.1.295, the version the pin installs (base-action/action.yml:150andsrc/entrypoints/run.ts:80at2dca132f, read through the GitHub API on 2026-10-09; a 2.1.295 start record carriesclaude_code_version: "2.1.295", measured on a J8 headless stream the same day), and names another version, or none, as an unmet bound. It counts a tool-list entry that is not a string as a forbidden tool namednon-string tool entryinstead of filtering it out, and the usage record'stoolsfield keeps such an entry, namedother. The apply step refuses an empty proposal and builds its rows before the loop, so a jq failure stops the step. It refuses (exit 2) a row count that differs from the proposal's length. Bothgh apicalls read< /dev/null, so neither can take the loop's remaining rows. Its closing message and comment say "read, checked or labelled", and two new comment sentences name the rows and/dev/nullchanges. The guard step's comment now says that debug logging set as a repository secret or variable is bound and refused. R4's== 'true'bindings are unchanged and now tested per source as they are; widening them (follow-up F-01) stays a follow-up, as does the rest of the tool-list shape (F-02). Tests: eight new (test_claude_args_are_pinned_exactly,test_each_source_of_a_debug_setting_reaches_the_guard,test_a_gh_call_cannot_read_the_rows_the_loop_has_not_reached,test_the_rows_are_built_and_counted_before_the_loop,test_a_tool_entry_that_is_not_a_string_is_a_forbidden_tool,test_the_usage_record_lists_a_tool_entry_that_is_not_a_string_as_other,test_the_session_must_run_the_client_version_the_pin_installs,test_the_caps_hold_at_eight_turns_and_two_dollars_and_fail_just_above). Eight changed: the flag list moved into the exact pin, the whole--settingsobject is pinned, an empty-proposal case was added, the closing message is asserted, and the new caps are used. The test harness gains aghstand-in that reads stdin,Run.run_text, and agithub_expressionhelper that evaluates the binding the workflow holds. The module runs 35 tests (27 before), and 30 weakened copies each fail their named test. The record corrects "the repository's label set" to the fixed three-label allow-list and restates the effort, caps and bounds. It adds three "What would overturn it" triggers (caps re-derived from the first measured runs, effort back tolowon a parity check, the settings-source runs repeated on a new pin) and dates the replay of the action's argument parser as R4. Its dated R5 section names every change, the settings-source measurements, the shared agent-modeoriginURL sentence and the follow-ups line, and notes that the symbolic-link strip does not apply, since no pull request head is checked out.Settings sources, measured by the command center on the installed Claude Code 2.1.295 on 2026-10-09. Each run used a throwaway home directory, a marker model in the settings file under test, and a loopback stand-in for the API; the model in each request showed whether that file had loaded.
.claude/settings.json): loaded with no flags and with--setting-sources user,project,localalone. Not loaded with--restrictedalone,--restricted --setting-sources user,project,local(the list the action passes whenclaude_argsgives none),--restricted --setting-sources user, or--setting-sources useralone..claude/settings.local.json): the same as the project file.--restrictedkeeps it out under every source list, and--setting-sources useralone keeps it out.~/.claude/settings.json):--restrictedkeeps it out, even with--setting-sources user;--setting-sources userwithout--restrictedloads it. Under this workflow's flags, a user settings file the action writes is not loaded.--settingsJSON value: honoured under--restricted --setting-sources user, so the workflow's deny rules apply, as the 2026-10-09 deny probes also showed.So
--restrictedwins over any source list, and for the two repository files either flag holds if the other's behaviour changes. The flags are the same in all five PRs of this series.Base commit:
aba02ec3(the merge base, unchanged at 710d061; main had moved to1304004a(Record Codex native review trial and credential-store research #916) at the last local fetch, and onlymanifests/evidence.jsonoverlaps; the one rebase happens at landing)Lane:
lane:foundationOwned paths touched:
.github/workflows/claude-triage.yml(new),tests/test_claude_triage_workflow.py(new),tests/test_workflow_policy.py(two inventory entries, one exemption),tests/test_workflow_security_coverage.py(coverage set and one zizmor test),docs/decisions/2026-10-08-claude-actions-triage.md(new),docs/decisions/2026-10-04-ci-least-privilege.md(two write-grant rows, one per-workflow row and the exemption sentence edited in place, and a dated addendum),docs/github-automation.md(current-practice bullet),manifests/evidence.json(registration, last commit). R5 touches the workflow, its test module, the record and the manifest only.R6 (2026-10-09), at 5c74982
--json-schema, the pinned action returns no structured proposal from a budget stop (base-action/src/run-claude-sdk.ts:252-277at 2dca132f), so there is nothing to apply or publish. The usage record keepsresult_subtype, and the failure names the stop.--max-turns 8removed: the pinned action (run-claude-sdk.ts:241-250, since 6ef6450f) fails a success whosenum_turnsexceeds it. On Claude Code 2.1.295num_turnscounts transcript messages: 57 for 12 requests under--max-turns 12. That is a code reading plus a local measurement, not a hosted run. The numbers step's 1-to-8 assistant-turn check stays.validate.pyand the manifest check pass (10,904 files);SOTA sources
2dca132ff0e0c4094ce6048b422c6915a071210b(https://github.com/anthropics/claude-code-action/tree/2dca132ff0e0c4094ce6048b422c6915a071210b):action.yml(outputstructured_output),docs/usage.md("Structured Outputs":--json-schemainclaude_args; "Result is validated against your schema"),examples/issue-triage.yml(read and not followed: event trigger, any author, write token in the model's job),docs/security.md(allowed_non_write_users"is a significant security risk"),docs/setup.md(federation inputs). For R5:base-action/action.yml:150(CLAUDE_CODE_VERSION="2.1.295") andsrc/entrypoints/run.ts:80(claudeCodeVersion = "2.1.295"), read through the GitHub API on 2026-10-09;base-action/src/parse-sdk-options.ts(settingSources: the--setting-sourcesgiven inclaude_args, else user, project and local);base-action/src/setup-claude-code-settings.ts(the user settings file the action writes);src/github/operations/git-config.ts:129-134andsrc/modes/agent/index.ts:52-61(in agent mode theoriginURL carries the job token).claude --helpfor--json-schema,--restricted("ignores user, project and local settings files (managed settings and --settings still apply)"),--setting-sources,--permission-promptsand--effort(low, medium, high, xhigh, max); a local run of the flag set with--json-schemareturned structured output with the session tools Glob, Grep, Read and StructuredOutput; the command center's settings-source runs of 2026-10-09 on the same client (above).||returns the first truthy operand, else the last; strings compare ignoring case; an unset secret or variable reads as an empty string); not re-read in R5.c35393777e5604a63721d09512263b1383301d4f, README ("safe outputs": the agent proposes, a separate job writes): https://github.com/github/gh-aw/tree/c35393777e5604a63721d09512263b1383301d4f.gh issue list,gh pr list): https://cli.github.com/manual/; REST API, add labels to an issue: https://docs.github.com/en/rest/issues/labels#add-labels-to-an-issue; all read 2026-10-08.Evidence-class table
native_proven, one local run on the installed 2.1.295 client (subscription, Haiku; not through the action)synthetictests/test_claude_triage_workflow.py, 35 tests at 710d061 (27 at e1ba3bb), all of them run without PyYAML too; each step's shell is taken from the workflow file and executed against a local stand-in forghsynthetic--restrictedkeeps the user, project and local settings files out under any source list,--setting-sources useralone keeps the project and local files out, and--settingsstill applieslocal_integrationsource_review, with one measured start recordbase-action/action.yml:150andsrc/entrypoints/run.ts:80at2dca132f, read through the GitHub API on 2026-10-09; a J8 headless stream on 2.1.295, 2026-10-09local_integrationactionlint1.17.0 exit 0, run without ShellCheck (not installed on the host that ran the R5 checks; hosted validate runs it);zizmor1.30.1 offline, pedantic and regular with--no-ignores, no findings (two auditor-onlysecrets-outside-envfindings on the R4 secret reads suppressed by persona). Earlier heads: actionlint with ShellCheck 0.9.0, and at R4 the action's argument parser replayed on the workflow text, including the JSON schema (not repeated at R5)Local commands run
The first head (096ad1a) failed hosted validate on one ShellCheck note (SC2016, a jq program kept in a shell variable); the second head keeps every jq program literal and passed actionlint with ShellCheck 0.9.0 locally. R5's new shell lines keep that form: every expansion is quoted, and the only single-quoted programs are arguments to jq and awk. Hosted checks at 710d061: not read yet; they run after the push.
Decision record
docs/decisions/2026-10-08-claude-actions-triage.md(two jobs so the model never holds a write scope, what an injected issue can do, effort, cost, changed test contracts, alternatives, what would overturn it). Its R5 section (2026-10-09) names every R5 change by name, with the settings-source measurements, the tests, the 30 weakened copies and the follow-ups.docs/decisions/2026-10-04-ci-least-privilege.mdlists both jobs in its write-grant table and the workflow in its per-workflow table, names each exempt job in its federation-exemption sentence, and gets a dated addendum naming both jobs.Host evidence
Not applicable: no file under
evidence/hosts/changes.Checklist
version comment (no floating tags).
permissions: {}and grant each jobonly what it needs (
contents: read, or an explicitly justified addition).added without a documented owner.
moved or overwritten).
Activation is a separate step after landing: an owner sets
CLAUDE_TRIAGE_ENABLEDtotrue. Runs authenticate by workload identity federation and spend from the Console organization the fourANTHROPIC_*repository variables name; no Anthropic key is stored in GitHub. Each run has a $2 client budget and the job's 15-minute timeout while it lasts, and fails afterwards if it took more than 8 assistant turns (there is no client turn limit since R6); it is expected to cost about $0.40 until measured runs replace the estimate. This PR, #892, #894 and #895 all edit the write-grant inventory and the evidence manifest, so each later one rebases once.R7 (2026-10-09, head fea7821)
Fixes from the J8 micro read of the R5 and R6 delta (
e1ba3bb3..19cf2a0a):successsubtype whoseis_erroris not false is named in the failure message;--max-turnscheck reads the raw text, and the $2 case asserts no overrun message.Findings 4 and 6 are dispositioned in the record's R7 section. 37 tests; five weakened copies each fail; validate rc 0; zizmor pedantic and actionlint rc 0.
🤖 Generated with Claude Code