Repository navigation
fix(plugin-eval): read the eval probe's stream line by line and name its facts on a failed denial check - #13640
Conversation
…its facts on a failed denial check Since the eval legs moved to `--output-format stream-json --verbose` (#13626), the probe's denial check slurped the whole stream with `jq -s`, which fails on any stdout line that is not JSON. A failed parse reads as "the managed policy did not record a denied Read", and nothing the run logs can tell that apart from a policy that let the read through, a model that made no call, or an API error (run 42146761553, both baseline key legs). - The check now parses each line with `fromjson?`, so a line that is not JSON is skipped. The check is unchanged otherwise and still fails closed. - A failed check now names the probe's closed facts: - whether a result line exists, its subtype, terminal_reason, is_error, api_error_status and num_turns; - the tools called, the denial count and the count of lines that are not JSON. It names no text the model or a tool wrote. - Test: every eval leg in both workflows runs the same managed-policy step and the same probe, and the probe parses line by line. Refs: #13586 Refs: #12035 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-read # Conflicts: # .github/actions/resolve-config/callers.test.mjs
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
claude-security-review reviewed Security review: PR #13640 (incremental, head 80b30ff)No security issues found. This review covered only what changed since Scope
What changed
Other checks
critical-findings: 0 |
|
claude-review reviewed Code review: PR #13640, incremental review since
|
indexOf returns -1 for a renamed step, which compares smaller than any real index, so the deny-before-isolate check passed without checking. Each step name must now be found before the order is compared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-review's advisory ( |
Refs: #13586
Refs: #12035
Summary
In run 42146761553 (education/teach, case te-*), both baseline passes' key legs failed at the safety probe with "the managed policy did not record a denied Read of /proc/self/environ, so no case runs". The subscription and fallback legs were skipped. This PR fixes the one defect the evidence shows in the check, and makes the next failure name its cause. The probe still fails closed.
Fix
What changed in the split. I compared the probe with the pre-#13626 single-leg version (
git show 60f5136484:.github/workflows/eval-hill-climb-pass.yml):--output-format json(one object) tostream-json --verbose, so the classifier can read a credit or limit stop.jq -e -s ... 'last(.[] | select(.type == "result")) ...'. That slurp fails to parse when any stdout line is not JSON. A parse failure reads exactly as "did not record a denied Read".Evidence:
claude -p(Claude Code 2.1.318) with a deny rule records the denial inpermission_denialsunder bothjsonandstream-json --verbose, so the stream format itself still carries the field.The cause on that runner is UNVERIFIED. The probe writes its stream to a file that is never printed, by design, so the log cannot separate:
I found nothing showing a difference in Claude Code behavior between credential types.
The fix.
Parse. The check parses the stream line by line (
jq -R -s '[split("\n")[] | fromjson? | objects] | ...'), so a line that is not JSON is skipped. It still requires all four denials on the lastresultline, and still fails closed.Diagnostics. A failed check now names the probe's closed facts:
resultline exists;subtype,terminal_reason,is_error,api_error_statusandnum_turns;Read,Grep,Globorother), the denial count, and the count of lines that are not JSON.Every value goes through a closed pattern, so no model or tool text reaches the public log. The next run that fails says which of the four cases it is.
Scope. The same change goes into all six probe steps, in
eval-hill-climb-pass.ymlandeval-browser-dispatch.yml.Verification
New test in
callers.test.mjs:jq -e -s, and its failure message carriesprobe_facts.Simulated probe steps. Every leg's rendered probe step ran in bash with a stub
claude:tool_calls=none denials=0 is_error=false;is_error=true api_error_status=404 terminal_reason=api_error.Node suites. After merging main (ci(lanes): run the mutation lane on run-claude-cli, mutants in a job with no secret #13632): 8227 tests, 8227 pass, 0 fail, 0 skipped.
callers.test.mjsruns 262 of 262.Bash suites:
write-runner-never-rules.test.shcheck-artifact-retention.test.shcheck-app-key-references.test.shcheck-read-caller-keys.test.shcheck-lane-coverage.test.shworkflow-self-paths.test.shcredential-sweep.test.shrun-zizmor.test.shci-review-lanes.test.shaffected-tests-live.shGates. runner-policy passed, and artifact-retention, read-caller-keys, app-key-references, actionlint and zizmor are clean.
Remaining
Related
🤖 Generated with Claude Code