Skip to content

feat(run-claude-cli): write the structured output to a scanned file - #861

Merged
kyle-sexton merged 1 commit into
mainfrom
feat/run-claude-cli-structured-output-file
Oct 23, 2026
Merged

kyle-sexton merged 1 commit into
mainfrom
feat/run-claude-cli-structured-output-file

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: first step of moving the Claude review lanes onto the run-claude-cli launcher; the new reusable review workflow reads this output.

Summary

A lane that passes json-schema-file to run-claude-cli can today get the model's structured_output only from execution-file, which the action documents as never scanned for credentials. A downstream job that publishes findings needs the structured output from a scanned source.

Fix

  • lane.cjs run writes the final result's structured_output to structured-output.json in the work directory and sets the new structured-output-file output, but only when json-schema-file is set, lane-class is empty, the result carries an object, and holdsCredential (the same scan the log gets) finds nothing.
  • A scan hit leaves the output empty, writes no file, and fails the step with the structured output held a credential and was not written, after the outputs and the log are written.
  • action.yml declares the output and points json-schema-file at it; the README run-claude-cli section says to read the structured output from that file, never from execution-file.
  • No CLI flag changes: --permission-prompts none and every other argument are untouched.

Verification

  • node --test .github/actions/run-claude-cli/*.test.cjs: 72 pass, 0 fail. Three new cases: a successful leg writes exactly the fixture's object; no schema, a max-turns leg, and a non-object each leave the output empty and write no file; the credential value and a key-shaped string each leave it empty and fail the step while the log is still written.
  • Mutation check: replacing the scan and the success guard with constants fails both negative cases.
  • The straggler case now checks that the action's last output line is structured-output-file=, so nothing a leftover leg process writes lands after it.
  • lane-outcome-emitters and intake-triage-legs tests: 27 pass. actionlint, zizmor, markdownlint, typos and gitleaks: clean.

Related

  • Consumer: the reusable review workflow that follows this PR in ci-workflows.

🤖 Generated with Claude Code

A lane that passes json-schema-file had to read structured_output from
execution-file, which is never scanned for credentials. The action now
writes the final result's structured_output to structured-output-file,
only on a successful leg with json-schema-file set and only after the
same holdsCredential scan the log gets; a hit leaves the output empty
and fails the step. No CLI flag changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 23, 2026 02:22
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T02:24:38.421777Z f2028f8 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 58s —— View job


Claude's code review

  • Gather context (diff, CLAUDE.md, REVIEW.md, path-scoped rules, standards index)
  • Review action.yml
  • Review lane.cjs
  • Review lane.test.cjs
  • Review README.md
  • Check path-scoped rules and standards index
  • Post findings

Scope: this repo runs a dedicated security lane (pr-review-security.yml), so per REVIEW.md this lane omits security findings and covers correctness, design, error handling, tests, and docs. .claude/rules/pr-body-contract.md carries no paths: frontmatter, so it is not a path-scoped rule here; separately, the PR body already satisfies it (No related issue: ..., plus Summary/Fix/Verification/Related). The standards index (docs/standards/README.md) has no paved-path row, so nothing applies there. No related issue is linked, so the closing-issue criteria don't apply.

Findings: none.

I traced the new structured-output-file path end to end:

  • structuredOutputText (lane.cjs:1159-1170) correctly gates on jsonSchema !== "", laneClass === "", excludes null (needed since typeof null === "object"), and only accepts object-shaped structured_output.
  • run() (lane.cjs:1172-1226) scans the structured text with the same holdsCredential/secret-value check the log already uses, writes outputs before throwing on a scan hit (consistent with the pre-existing log-file pattern), and the straggler test confirms output-key ordering (lane.test.cjs:904-922).
  • action.yml and README.md wording matches the implementation (gating conditions, "never read execution-file" guidance, scan-then-fail behavior).
  • New tests (lane.test.cjs:1392-1467) cover: success writes the exact object, no-schema/max-turns/non-object all leave it empty, and a credential or key-shaped string in the structured output empties it and fails the step while the log is still written.

I couldn't execute node --test myself in this sandbox (the run required interactive approval I don't have), so the PR's "72 pass, 0 fail" and the mutation-check claims are author-reported and unverified by me — the static trace above is independent of those claims. No concrete bug, missing-coverage gap, or doc/code mismatch rose to this lane's high-signal bar.

claude-lane-summary: lane=claude-review sha=f2028f810fe571085ba1152dcf589e2d00b438ea run=39889263889

@claude

claude Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 32s —— View job


Security review: no findings

Reviewed the full diff (.github/actions/run-claude-cli/action.yml, lane.cjs, lane.test.cjs, README.md) against origin/main.

What changed: lane.cjs adds structuredOutputText, extracting the final result's structured_output from the leg's execution file only when json-schema-file is set and lane-class is empty, then writes it to a fixed path (structured-output.json in the work directory, L1195) only after passing it through the same holdsCredential scan (L557) already used for the execution log, with the same secret value and key-shape checks. action.yml wires a new structured-output-file output through the existing steps.run.outputs plumbing; no new shell interpolation of leg/model-controlled data was introduced.

Checked for:

  • Path handling — structuredFile is a fixed, non-attacker-controlled path under config.workDir; no traversal or injection surface.
  • Credential scanning consistency — the new file goes through the identical scan (same holdsCredential, same config.leg.value) as the existing log, and a scan hit fails the step before anything is written, matching the PR's stated behavior and the new tests.
  • Output injection — writeOutputs still rejects any output value containing a newline, so the new structured-output-file output can't be used to smuggle extra key=value lines into GITHUB_OUTPUT.
  • Trust boundary docs — README/action.yml now correctly steer consumers to the scanned structured-output-file instead of the unscanned execution-file for structured_output, closing the gap described in the PR body rather than widening it.
  • Instruction-surface deletions — no CLAUDE.md/AGENTS.md/rules files or plugin manifests are touched by this diff, so that check doesn't apply.

No logic flaws, authorization gaps, injection surfaces, or unsafe credential handling found. GitHub Actions hardening concerns (triggers, permissions, pin integrity) are out of scope for this lane and deferred to zizmor, which this PR's author notes ran clean.

claude-lane-summary: lane=claude-security-review sha=f2028f810fe571085ba1152dcf589e2d00b438ea run=39889263889

@github-actions

Copy link
Copy Markdown

claude-security-review has reviewed this pull request through f2028f8; a later push is reviewed from there.

@github-actions

Copy link
Copy Markdown

claude-review has reviewed this pull request through f2028f8; a later push is reviewed from there.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit beee529 Oct 23, 2026
55 of 58 checks passed
@kyle-sexton
kyle-sexton deleted the feat/run-claude-cli-structured-output-file branch October 23, 2026 02:32
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