Repository navigation
fix(view-runtime): print the per-user medium clause only for plugins that ship a medium option #14442
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pr-review-security-hosted | |
| # Locally owned: the `claude-security-review-hosted-caller` component of | |
| # melodic-software/standards | |
| # (components/claude-lanes-hosted/claude-security-review.yml) is | |
| # `locally-owned` for this repository in distribution/sync-manifest.yml, so | |
| # this copy can add the resolve-model and kill-switch jobs below. It differs | |
| # from the component in those jobs and these comments. | |
| # | |
| # Thin caller for the org's pr-review-cli reusable, lane `security` (advisory | |
| # verdict, fail-closed execution check). The reusable owns the launcher, the | |
| # credential order (the reviews role's API key, then its subscription token | |
| # after a handoff) and the secret-handling model; per its header, this caller | |
| # owns the triggers, concurrency, and the GITHUB_TOKEN permission grant. | |
| # | |
| # There is no workflow-level path gating. The `docs-only-paths` passed below | |
| # is empty, so a documentation-only pull request is reviewed too. A | |
| # `.github/claude-security-paths` file is no longer read. | |
| # | |
| # NEVER add workflow-level `paths:`/`paths-ignore:` to this caller: a check | |
| # whose workflow never triggers is missing, not green, so a required check | |
| # would stay Pending forever, and the babysit merge gate, which waits for | |
| # `pr-review-security / security-review`, never arms. | |
| # | |
| # The reusable skips fork PRs (they receive no secrets and a read-only token), | |
| # Dependabot, and every `[bot]` actor whose sender id is not in | |
| # `allowed-bot-sender-ids` (below); a required check does NOT prove a fork PR | |
| # was reviewed (see the reusable's header). On a public repository the lane | |
| # reviews only pull requests from branches of this repository: fork and | |
| # Dependabot pull requests get no secrets. | |
| # | |
| # RUNNER PLACEMENT: every job of the reusable runs on ubuntu-24.04; it takes | |
| # no runner input. | |
| on: | |
| pull_request: | |
| # `synchronize`, matching the code-review lane: every push is re-reviewed, | |
| # including the push that resolves a merge conflict (GitHub runs no | |
| # pull_request workflow while a PR conflicts with its base), narrowed by | |
| # the reusable to the files changed since the last completed review. No | |
| # ruleset requires the check; ci-status, the one required check, needs its | |
| # success on the head and fails while the hold label is on (github-iac | |
| # ADR 0017). | |
| # | |
| # NO `merge_group:`: the reusable runs only on `pull_request`; see | |
| # melodic-software/ci-workflows#272 before enabling a merge queue. | |
| types: [opened, synchronize, ready_for_review, reopened] | |
| permissions: | |
| contents: read | |
| # Per-PR group: a newer event for the same PR waits for the running review | |
| # instead of cancelling it, because the guardrails merge guard reads a | |
| # cancelled run as red. The group is the `${{ github.workflow }}`-prefixed | |
| # canonical form the standards `concurrency-policy` component requires. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: false | |
| jobs: | |
| # The review's model, turns and effort: the review-security route of the | |
| # models block in docs/conventions/pr-pipeline.yaml, read from the default | |
| # branch, never the PR head, so a PR cannot choose the model through its | |
| # own pr-pipeline.yaml (an edit to this file still shows in its diff). When | |
| # this job fails, the review still runs on the reusable's own | |
| # defaults, so the security-review check keeps reporting. | |
| resolve-model: | |
| if: github.event.pull_request.draft == false | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read # check out the default branch for the models block | |
| outputs: | |
| claude-args: ${{ steps.models.outputs.claude-args }} | |
| env: | |
| API_BUDGET_TOKEN: github-token | |
| steps: | |
| - name: Resolve main's tip | |
| id: tip | |
| env: | |
| TIP_BRANCH: main | |
| GIT_TERMINAL_PROMPT: "0" | |
| run: | | |
| sha=$(timeout 60 git -C "$RUNNER_TEMP" ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "refs/heads/$TIP_BRANCH" | | |
| awk -v ref="refs/heads/$TIP_BRANCH" '$2 == ref { print $1 }') | |
| [[ $sha =~ ^[0-9a-f]{40}$ ]] || { echo "::error::the tip of $TIP_BRANCH did not resolve"; exit 1; } | |
| echo "sha=$sha" >>"$GITHUB_OUTPUT" | |
| - name: Check out the default branch | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ steps.tip.outputs.sha }} | |
| path: .base | |
| persist-credentials: false | |
| # resolve-config imports the two check-* action modules. | |
| sparse-checkout: | | |
| /.github/actions/check-kill-switch/ | |
| /.github/actions/check-trusted-trigger/ | |
| /.github/actions/log-api-budget/ | |
| /.github/actions/resolve-config/ | |
| /.github/actions/setup-pinned-node/ | |
| /.github/CODEOWNERS | |
| /.github/standards/github-actions-conventions/vocabulary.json | |
| /.node-version | |
| /docs/conventions/pr-pipeline.yaml | |
| /docs/conventions/pr-pipeline/ | |
| sparse-checkout-cone-mode: false | |
| - name: Set up Node | |
| uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] | |
| with: | |
| node-version-file: .base/.node-version | |
| - name: Resolve the review model | |
| id: models | |
| uses: ./.base/.github/actions/resolve-config # zizmor: ignore[self-repository] | |
| with: | |
| model-route: review-security | |
| output-path: ${{ runner.temp }}/review-models.json | |
| # The reusable takes Claude args only; a subagent model would be dropped. | |
| - name: Refuse a subagent model | |
| env: | |
| SETTINGS_JSON: ${{ steps.models.outputs.settings-json }} | |
| run: | | |
| if [ -n "$SETTINGS_JSON" ]; then | |
| echo "::error::the review-security profile sets subagent-model, which pr-review-cli.yml does not support" | |
| exit 1 | |
| fi | |
| - name: Log the API budget at the end | |
| if: always() | |
| continue-on-error: true | |
| timeout-minutes: 1 | |
| uses: ./.base/.github/actions/log-api-budget # zizmor: ignore[self-repository] | |
| with: | |
| tokens: github-token | |
| # The reusable reads no kill switch, so this caller does. The org | |
| # and repository switches fail closed (check-kill-switch); the lane's own | |
| # CLAUDE_SECURITY_REVIEW_DISABLED stops it only at `true`, as github-iac's | |
| # README documents for the review-lane switches. A failed job leaves proceed | |
| # empty, which also stops the review. | |
| kill-switch: | |
| if: github.event.pull_request.draft == false | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read # check out the default branch for the kill-switch action | |
| outputs: | |
| proceed: ${{ steps.kill.outputs.proceed == 'true' && steps.upstream.outputs.degraded != 'true' && vars.CLAUDE_SECURITY_REVIEW_DISABLED != 'true' && 'true' || 'false' }} | |
| env: | |
| API_BUDGET_TOKEN: github-token | |
| steps: | |
| - name: Resolve main's tip | |
| id: tip | |
| env: | |
| TIP_BRANCH: main | |
| GIT_TERMINAL_PROMPT: "0" | |
| run: | | |
| sha=$(timeout 60 git -C "$RUNNER_TEMP" ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "refs/heads/$TIP_BRANCH" | | |
| awk -v ref="refs/heads/$TIP_BRANCH" '$2 == ref { print $1 }') | |
| [[ $sha =~ ^[0-9a-f]{40}$ ]] || { echo "::error::the tip of $TIP_BRANCH did not resolve"; exit 1; } | |
| echo "sha=$sha" >>"$GITHUB_OUTPUT" | |
| - name: Check out the default branch | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ steps.tip.outputs.sha }} | |
| path: .base | |
| persist-credentials: false | |
| sparse-checkout: | | |
| /.github/actions/check-kill-switch/ | |
| /.github/actions/check-upstream-status/ | |
| /.github/scripts/maintenance-reconcile-lost-runs/component-status.mjs | |
| /.github/actions/setup-pinned-node/ | |
| /.node-version | |
| /.github/actions/log-api-budget/ | |
| sparse-checkout-cone-mode: false | |
| - name: Set up Node | |
| uses: ./.base/.github/actions/setup-pinned-node # zizmor: ignore[self-repository] | |
| with: | |
| node-version-file: .base/.node-version | |
| - name: Check the kill switch | |
| id: kill | |
| uses: ./.base/.github/actions/check-kill-switch # zizmor: ignore[self-repository] | |
| with: | |
| value: ${{ vars.CLAUDE_LANES_DISABLED }} | |
| repo-value: ${{ vars.CLAUDE_LANES_REPO_DISABLED }} | |
| # Skips the run while Actions or npm installs list an outage, with a | |
| # logged reason; fails open (.github/actions/check-upstream-status). | |
| - name: Check upstream status | |
| id: upstream | |
| if: steps.kill.outputs.proceed == 'true' | |
| continue-on-error: true | |
| timeout-minutes: 2 | |
| uses: ./.base/.github/actions/check-upstream-status # zizmor: ignore[self-repository] | |
| - name: Report the kill switch | |
| if: steps.kill.outputs.proceed != 'true' || vars.CLAUDE_SECURITY_REVIEW_DISABLED == 'true' | |
| env: | |
| REASON: ${{ steps.kill.outputs.reason }} | |
| LANE_SWITCH: ${{ vars.CLAUDE_SECURITY_REVIEW_DISABLED }} | |
| run: | | |
| echo "::notice::a kill switch stops this security review (lanes: ${REASON:-unreadable}, CLAUDE_SECURITY_REVIEW_DISABLED: ${LANE_SWITCH:-unset})" | |
| # A skip fails this job, so the run is a failed first attempt: the | |
| # lost-run reconciler re-runs it once GitHub Actions is operational | |
| # (reconcile.mjs `upstream`), the merge gate binds that re-run on this | |
| # error (babysit_review_evidence UPSTREAM_STOP_RE), and ci-status names | |
| # the stop. The reason is the gate's own one-line output. | |
| - name: Stop on upstream status | |
| if: steps.kill.outputs.proceed == 'true' && steps.upstream.outputs.degraded == 'true' && vars.CLAUDE_SECURITY_REVIEW_DISABLED != 'true' | |
| env: | |
| REASON: ${{ steps.upstream.outputs.reason }} | |
| run: | | |
| echo "::error::an upstream outage stops this security review (${REASON:-unknown}); the lost-run reconciler re-runs it once GitHub Actions is operational" | |
| exit 1 | |
| - name: Log the API budget at the end | |
| if: always() | |
| continue-on-error: true | |
| timeout-minutes: 1 | |
| uses: ./.base/.github/actions/log-api-budget # zizmor: ignore[self-repository] | |
| with: | |
| tokens: github-token | |
| pr-review-security: | |
| needs: [resolve-model, kill-switch] | |
| # A draft is work in progress and does not spend a review; the | |
| # ready_for_review trigger above is what delivers one when it is ready. | |
| if: ${{ !cancelled() && github.event.pull_request.draft == false && needs.kill-switch.outputs.proceed == 'true' }} | |
| # The model jobs narrow this to contents: read and pull-requests: read; | |
| # only the reusable's status job (the security-review check) writes. | |
| permissions: | |
| actions: read # the reusable's check-key-credit job reads this | |
| # workflow's runs for the key's recent credit stop, | |
| # and its status job reads them for cost decisions | |
| checks: read # the reusable's status job reads its earlier | |
| # runs' cost notices | |
| contents: read | |
| pull-requests: write # the review, its inline comments and the lane's | |
| # reviewed-head marker comment | |
| uses: melodic-software/ci-workflows/.github/workflows/pr-review-cli.yml@main | |
| with: | |
| lane: security | |
| publish: comments | |
| lane-transcript-public-key: ${{ vars.LANE_TRANSCRIPT_PUBLIC_KEY }} | |
| # Empty unless resolve-model succeeded, so a failed job's partial output | |
| # never reaches the review. | |
| model-args: ${{ needs.resolve-model.result == 'success' && needs.resolve-model.outputs.claude-args || '' }} | |
| # Bot senders this lane reviews, by numeric user id: the | |
| # automation-lanes App, so a commit it pushes to a pull request is | |
| # reviewed. Where the repository does not set this variable the value is | |
| # empty and the reusable reviews no bot. | |
| allowed-bot-sender-ids: ${{ vars.AUTOMATION_LANES_APP_SENDER_ID }} | |
| # Empty: the security lane reviews every change, documentation included. | |
| # A skipped review binds no evidence to the head, so pr-merge scored a | |
| # docs-only pull request medium and held it for a person; a review with | |
| # no findings binds to the head and scores low. | |
| docs-only-paths: '' | |
| # Copies scripts/sync-shared-copies.sh generates, read at the merge base: | |
| # identical changes to copies of one source are read once, hooks included. | |
| generated-copies: scripts/shared-copies.txt | |
| # Generated data files counted as read, read at the merge base. | |
| generated-data: scripts/review-generated-data.txt | |
| # A hold adds this label and a `claude-lane-hold` marker comment; ci-status | |
| # fails while the label is on. The review holds only for a real-charge | |
| # cost or a CRITICAL security finding; hold-paths names the files the | |
| # prompt sends to extra security scrutiny and never holds by itself. | |
| # scripts/sync-hold-floor.py writes the list from the floor, | |
| # the same in both callers; ci-status's exempt-author hold_patterns name | |
| # the same paths less the security review's own files, which are | |
| # scrutiny only. | |
| hold-label: needs-human | |
| # BEGIN hold-floor hold-paths: generated by scripts/sync-hold-floor.py | |
| hold-paths: | | |
| .github/standards/trusted-actors/** | |
| **/CODEOWNERS | |
| docs/conventions/pr-pipeline.yaml | |
| **/.gitmodules | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_risk.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_check_inputs.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_checks.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_classify.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_dependabot.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_feedback.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_gh.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_merge_policy.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_postmerge.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_repo_config.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_review_evidence.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_review_trigger.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_run_cache.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_state.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_tracker_binding.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_util.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_verify_evidence.py | |
| plugins/source-control/skills/babysit-prs/scripts/babysit_whole_review.py | |
| **/.claude/** | |
| docs/conventions/source-control.yaml | |
| **/.claude-lane/** | |
| **/.mcp.json | |
| **/CLAUDE.md | |
| **/CLAUDE.local.md | |
| **/AGENTS.md | |
| REVIEW.md | |
| .github/actions/check-trusted-trigger/** | |
| .github/actions/resolve-config/** | |
| .github/actions/check-kill-switch/** | |
| .github/actions/request-lane-token/** | |
| .github/actions/log-api-budget/rate-guard.mjs | |
| .github/actions/log-api-budget/api-budget.bash | |
| .github/actions/log-api-budget/rate-limits.json | |
| .github/scripts/pr-merge/merge-holds.mjs | |
| .github/scripts/pr-merge/enable-auto-merge.mjs | |
| .github/scripts/pr-merge/superseded.mjs | |
| .github/scripts/pr-merge/clear-escalations.mjs | |
| .github/scripts/pr-merge/carry-run-cache.mjs | |
| .github/scripts/pr-merge/gate-check.mjs | |
| .github/scripts/pr-merge/head-ref-map.mjs | |
| .github/scripts/pr-merge/lane-records.mjs | |
| .github/scripts/pr-merge/last-report.mjs | |
| .github/scripts/pr-merge/rate-stop.bash | |
| .github/scripts/pr-merge/retry-call.bash | |
| .github/scripts/pr-merge/refire-refused.mjs | |
| .github/scripts/pr-merge/rotation.mjs | |
| .github/scripts/pr-merge/sweep-summary.mjs | |
| .github/scripts/pr-merge/who.mjs | |
| .github/scripts/pr-merge/post-unarmed-status.mjs | |
| .github/scripts/pr-merge/verify-answers.mjs | |
| .github/actions/report-check-run/posting-run.mjs | |
| scripts/sync-hold-floor.py | |
| .github/scripts/pr-unblock/address-feedback.mjs | |
| .github/scripts/pr-unblock/escalation-routes.mjs | |
| .github/scripts/pr-unblock/escalation-routes.json | |
| .github/scripts/pr-unblock/gate-source.mjs | |
| .github/scripts/pr-unblock/thread-scope.mjs | |
| .github/scripts/pr-unblock/retry-fetch.mjs | |
| .github/scripts/work-resolve-issue/report-outcome.mjs | |
| .github/scripts/work-resolve-issue/limits.mjs | |
| .github/scripts/work-resolve-issue/retry-fetch.mjs | |
| .github/scripts/pr-unblock/report-escalation.sh | |
| .github/scripts/pr-unblock/adjudicate-feedback.mjs | |
| .github/scripts/pr-unblock/publish-feedback.mjs | |
| .github/scripts/pr-unblock/resolve-threads.mjs | |
| .github/scripts/pr-unblock/request-budget.mjs | |
| .github/scripts/pr-verify/resolve-pr.mjs | |
| .github/scripts/pr-verify/write-verdict.mjs | |
| .github/scripts/intake-triage/admit.mjs | |
| .github/actions/select-trusted-text/select-trusted-text.mjs | |
| lib/publish-gate.mjs | |
| .github/recurring-schedule.json | |
| plugins/work-items/scripts/charge-words.json | |
| .github/scripts/pr-unblock/dependency-classify.mjs | |
| .github/scripts/apply-patch/apply-patch.mjs | |
| .github/scripts/apply-patch/predicates.mjs | |
| .github/scripts/apply-patch/protected-paths.mjs | |
| .github/scripts/apply-patch/retry-fetch.mjs | |
| .github/scripts/pr-unblock/fix-ci-context.mjs | |
| .github/scripts/pr-unblock/feedback-dispatch.mjs | |
| .github/scripts/pr-unblock/verify-notes.mjs | |
| .github/scripts/pr-unblock/earlier-fail-notes.mjs | |
| .github/scripts/pr-unblock/limit-stop.mjs | |
| .github/scripts/lib/lane-outcome.mjs | |
| .github/scripts/pr-unblock/dispatch-record.mjs | |
| .github/scripts/pr-unblock/verify-cap.mjs | |
| .github/scripts/maintenance-reconcile-lost-runs/infra-failure.mjs | |
| .github/scripts/work-resolve-issue/stop-routes.json | |
| .github/scripts/work-resolve-issue/stop-routes.mjs | |
| .github/scripts/work-resolve-issue/known-names.json | |
| .github/scripts/lib/stop-reason.mjs | |
| .github/scripts/lib/admission-comment.mjs | |
| .github/scripts/lib/done-evidence.mjs | |
| .github/scripts/pr-review-main/** | |
| docs/conventions/instruction-exception-register/** | |
| docs/conventions/untrusted-content/** | |
| docs/standards/** | |
| docs/review/** | |
| .github/workflows/dependabot-fragments.yml | |
| .github/workflows/eval-browser-dispatch.yml | |
| .github/workflows/intake-reenter.yml | |
| .github/workflows/intake-triage.yml | |
| .github/workflows/maintenance-audit-lane-runs.yml | |
| .github/workflows/maintenance-reconcile-lost-runs.yml | |
| .github/workflows/maintenance-tidy-code-upkeep.yml | |
| .github/workflows/post-merge-demotion-alert.yml | |
| .github/workflows/post-merge-test-mutation.yml | |
| .github/workflows/pr-feedback-dispatch.yml | |
| .github/workflows/pr-merge.yml | |
| .github/workflows/pr-refine.yml | |
| .github/workflows/pr-require-checks.yml | |
| .github/workflows/pr-review-approved.yml | |
| .github/workflows/pr-review-comment-relay.yml | |
| .github/workflows/pr-review-hosted.yml | |
| .github/workflows/pr-review-main.yml | |
| .github/workflows/pr-review-security-hosted.yml | |
| .github/workflows/pr-run-activity-read.yml | |
| .github/workflows/pr-run-activity-write.yml | |
| .github/workflows/pr-unblock-dispatch.yml | |
| .github/workflows/pr-unblock.yml | |
| .github/workflows/pr-verify-capture.yml | |
| .github/workflows/pr-verify.yml | |
| .github/workflows/release-publish-plugins.yml | |
| .github/workflows/upstream-apply-changelog.yml | |
| .github/workflows/work-resolve-issue.yml | |
| .github/workflows/work-unblock.yml | |
| plugins/review/skills/security-review/** | |
| plugins/review/evals/security-review-*/** | |
| # END hold-floor hold-paths | |
| # The reviews role's key runs first; its subscription token only after the | |
| # key hands off, and the central fallback token only after the | |
| # subscription token hands off. Named secrets only, never `secrets: inherit`. | |
| secrets: | |
| CLAUDE_API_KEY: ${{ secrets.REVIEWS_CLAUDE_API_KEY }} | |
| CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.REVIEWS_CLAUDE_CODE_OAUTH_TOKEN }} | |
| FALLBACK_CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.FALLBACK_CLAUDE_CODE_OAUTH_TOKEN }} |