[chore](ci) refactor code review workflow to run on PR events with comment-triggered dispatch#62302
[chore](ci) refactor code review workflow to run on PR events with comment-triggered dispatch#62302zclllyybb wants to merge 1 commit intoapache:masterfrom
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
|
/review |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Refactors the automated code review GitHub Actions setup so the main review workflow is visible on PR events, while the actual review execution is dispatched via a /review PR comment.
Changes:
- Split review execution into a reusable workflow (
workflow_call) and a comment-driven dispatcher workflow. - Make the “Code Review” workflow appear on PR events as a stable (potentially required) check without running the reviewer unless dispatched.
- Update contributor/agent guidance in
AGENTS.mdand expand review checkpoints documentation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
AGENTS.md |
Updates developer guidance and adds a rule about keeping commits task-focused. |
.github/workflows/pr-approve-status.yml |
Removes the “Need_2_Approval” workflow. |
.github/workflows/opencode-review.yml |
Refactors review workflow: PR-triggered visibility + workflow_call-only execution. |
.github/workflows/opencode-review-comment.yml |
Adds /review comment dispatcher that resolves PR context and calls the reusable workflow. |
.claude/skills/code-review/SKILL.md |
Updates review checkpoint guidance and adds emphasis on concurrency reasoning and test results. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if: >- | ||
| github.event.issue.pull_request && | ||
| contains(github.event.comment.body, '/review') |
There was a problem hiding this comment.
As written, any user who can comment on a PR can trigger /review, and secrets: inherit will pass repository secrets into a workflow that checks out and processes untrusted PR code. This is a privilege-escalation risk (especially for PRs from forks). Recommended: (1) gate dispatch on github.event.comment.author_association (e.g., OWNER/MEMBER/COLLABORATOR) and/or an allowlist; (2) fetch PR metadata and refuse to run (or run without sensitive secrets) when .head.repo.fork == true or .head.repo.full_name != github.repository; and (3) avoid secrets: inherit—pass only the minimal required secrets explicitly.
| uses: ./.github/workflows/opencode-review.yml | ||
| secrets: inherit |
There was a problem hiding this comment.
As written, any user who can comment on a PR can trigger /review, and secrets: inherit will pass repository secrets into a workflow that checks out and processes untrusted PR code. This is a privilege-escalation risk (especially for PRs from forks). Recommended: (1) gate dispatch on github.event.comment.author_association (e.g., OWNER/MEMBER/COLLABORATOR) and/or an allowlist; (2) fetch PR metadata and refuse to run (or run without sensitive secrets) when .head.repo.fork == true or .head.repo.full_name != github.repository; and (3) avoid secrets: inherit—pass only the minimal required secrets explicitly.
| - If issues found, submit a review with inline comments plus a comprehensive summary body. Use GitHub Reviews API to ensure comments are inline: | ||
| - Inline comment bodies may include GitHub suggested changes blocks when you can propose a precise patch. | ||
| - Prefer suggested changes for small, self-contained fixes (for example typos, trivial refactors, or narrowly scoped code corrections). | ||
| - Do not force suggested changes for broad, architectural, or multi-file issues; explain those normally. | ||
| - Build a JSON array of comments like: [{ "path": "<file>", "position": <diff_position>, "body": "..." }] | ||
| - Submit via: gh api repos/PLACEHOLDER_REPO/pulls/PLACEHOLDER_PR_NUMBER/reviews --input <json_file> | ||
| - The JSON file should contain: {"event":"COMMENT","body":"<summary>","comments":[...]} | ||
| - Do not use: gh pr review --approve or --request-changes | ||
| - MUST clearly state your stance on PRs: execute `gh pr review --approve` if there are no significant issues, otherwise request changes with `gh pr review --request-changes`. |
There was a problem hiding this comment.
The prompt now instructs the agent to (a) submit a PR review via the Reviews API with event: COMMENT and (b) also run gh pr review --approve/--request-changes. This will create two separate reviews and can lead to confusing/duplicated outcomes (the API review won’t carry an approval state, and the CLI review likely won’t contain inline comments). Prefer a single mechanism: keep the GitHub Reviews API submission and set event to APPROVE or REQUEST_CHANGES (with inline comments included) so stance + inline comments are in one review.
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] |
There was a problem hiding this comment.
On pull_request events this job always succeeds after printing messages, so if branch protection marks “Code Review” as a required check, PRs can merge without an actual /review run. If the intent is gating, consider making the PR-event run fail/neutral until a dispatcher-run succeeds, or emit a separate status/check that the dispatcher updates (e.g., a dedicated check name for the dispatched review) and require that instead.
| - name: Keep required check visible on pull requests | ||
| if: ${{ github.event_name == 'pull_request' }} | ||
| run: | | ||
| PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}) | ||
| HEAD_SHA=$(echo "$PR_JSON" | jq -r '.head.sha') | ||
| BASE_SHA=$(echo "$PR_JSON" | jq -r '.base.sha') | ||
| HEAD_REF=$(echo "$PR_JSON" | jq -r '.head.ref') | ||
| BASE_REF=$(echo "$PR_JSON" | jq -r '.base.ref') | ||
| echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" | ||
| echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" | ||
| echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" | ||
| echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT" | ||
| echo "Code Review is exposed as a stable required check on pull requests." | ||
| echo "The actual automated review still runs only when the comment dispatcher invokes this workflow." |
There was a problem hiding this comment.
On pull_request events this job always succeeds after printing messages, so if branch protection marks “Code Review” as a required check, PRs can merge without an actual /review run. If the intent is gating, consider making the PR-event run fail/neutral until a dispatcher-run succeeds, or emit a separate status/check that the dispatcher updates (e.g., a dedicated check name for the dispatched review) and require that instead.
| @@ -1,8 +1,19 @@ | |||
| name: Code Review | |||
There was a problem hiding this comment.
The PR description focuses on refactoring the code review workflow/dispatcher, but this PR also deletes .github/workflows/pr-approve-status.yml (Need_2_Approval). If that removal is intentional, it should be called out in the PR description (and ideally reference what replaces its functionality, if anything).
There was a problem hiding this comment.
Found 2 issues that should be fixed before this workflow refactor lands.
.github/workflows/opencode-review.yml: the newpull_requestpath reports a successfulCode Reviewcheck after only printing twoecholines. Because every real review step is gated behindgithub.event_name == workflow_call, branch protection will see the required check as green even when no/reviewcomment was posted and no automated review ran.\n2..github/workflows/opencode-review-comment.yml: any public commenter can trigger/review.issue_commentruns in the base-repo context, and the called workflow inherits secrets plus write permissions, so external users can repeatedly burn the OpenCode/GitHub automation budget on arbitrary PRs.\n\nCritical checkpoint conclusions:\n- Goal of the task: split the workflow soCode Reviewstays visible on PRs while actual review is comment-dispatched. The current implementation does not safely achieve that goal, because the required check no longer proves a review ran and the dispatcher is not caller-restricted.\n- Small / focused change: the diff is localized, but it changes review-policy and workflow-security semantics in addition to event wiring.\n- Concurrency: no meaningful shared-state or lock-based concurrency concerns in these YAML changes.\n- Lifecycle: reusable-workflow lifecycle is straightforward; no special lifecycle hazards beyond the new two-path execution model.\n- Configuration items: no new runtime config items added.\n- Compatibility / parallel paths: there are now two parallel execution paths (pull_requestandworkflow_call), but only one performs the real review, which creates a behavioral gap.\n- Special conditions: the/reviewsubstring check is not sufficient as the sole gate; author permission checks are needed.\n- Test coverage: no automated validation was added for these workflow semantics.\n- Test result files: not applicable.\n- Observability: existing logging is not the main blocker here.\n- Transaction / persistence / data-write concerns: not applicable.\n- Performance: unrestricted/reviewtriggering can create unnecessary runner and API usage.\n- Other issues: none beyond the inline findings above.\n\nPlease address the inline comments before merging.
| echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" | ||
| echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" | ||
| echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" | ||
| echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
This turns Code Review into a misleading green required check. On pull_request events the job now succeeds after two echo statements, while every real review step is guarded by github.event_name == "workflow_call". That means a PR can show the required Code Review status as passing even if nobody ever comments /review and no automated review actually ran. Please keep the required check tied to a real review result, or make the non-review path fail/neutral instead of reporting success.
| resolve-pr: | ||
| runs-on: ubuntu-latest | ||
| if: >- | ||
| github.event.issue.pull_request && |
There was a problem hiding this comment.
This dispatcher is callable by any user who can comment on a public PR. Because issue_comment runs in the base-repo context and the called workflow uses secrets: inherit, an outside commenter can trigger secret-backed review runs and write-capable bot actions just by posting /review. Please gate this on github.event.comment.author_association (for example OWNER, MEMBER, or COLLABORATOR) before invoking the reusable workflow.
|
/review |
There was a problem hiding this comment.
I found 2 blocking issues.
-
Deleting
.github/workflows/pr-approve-status.ymlremoves the producer for theNeed_2_Approvalstatus, but.asf.yaml:65still lists that status as required onmaster. After this merges, PRs tomasterwill never be able to satisfy branch protection because the context will never be reported. Even if branch protection is updated separately, this PR also drops the existing two-approval policy down to GitHub's built-inrequired_approving_review_count: 1with no replacement. -
.github/workflows/opencode-review.ymlnow tells the automated reviewer to rungh pr review --approve/--request-changes. That is the opposite of this workflow's existing contract and the submission rules used by the review runner, which require either a plain PR comment or aCOMMENTreview created through the Reviews API. In practice the bot will mutate approval state directly instead of leaving non-blocking review comments.
Critical checkpoint conclusions:
- Goal of the task: Partially met. The dispatcher split itself is wired, but the overall change is not safe because approval enforcement and review semantics regress. There is no end-to-end test proving the new workflow arrangement preserves existing behavior.
- Scope and focus: Not sufficiently focused. Besides splitting the dispatcher, the PR also changes review-policy behavior and deletes an externally required status without preserving compatibility.
- Concurrency: No in-repo concurrency or locking concerns are introduced by these workflow-only changes.
- Lifecycle and compatibility: Broken for the external workflow/branch-protection contract around
Need_2_Approval. - Configuration items: No new configuration items are added.
- Parallel code paths: The comment-triggered dispatch path is connected, but the approval-status path has no replacement.
- Special conditional checks: The
github.event_namegating is straightforward and readable. - Test coverage: No automated validation of workflow behavior or branch-protection integration is included.
- Test results: No new test results were added or modified.
- Observability: Existing failure-comment handling is adequate for workflow failures.
- Transaction, persistence, data writes, FE/BE protocol: Not applicable to this PR.
- Performance: No material performance issue found beyond the functional regressions above.
- Other issues: None beyond the findings above.
| # specific language governing permissions and limitations | ||
| # under the License. | ||
| --- | ||
| name: Need_2_Approval |
There was a problem hiding this comment.
Deleting the only workflow named Need_2_Approval breaks master branch protection as currently configured: .asf.yaml:65 still lists that context under required_status_checks, and grep shows there is no other workflow left that publishes the same status. Once this lands, every PR to master will be missing a required check forever. If the intent is to retire this gate, the protection config has to change in the same PR; otherwise we need a replacement workflow/job that preserves the context.
| - Prefer suggested changes for small, self-contained fixes (for example typos, trivial refactors, or narrowly scoped code corrections). | ||
| - Do not force suggested changes for broad, architectural, or multi-file issues; explain those normally. | ||
| - Build a JSON array of comments like: [{ "path": "<file>", "position": <diff_position>, "body": "..." }] | ||
| - Submit via: gh api repos/PLACEHOLDER_REPO/pulls/PLACEHOLDER_PR_NUMBER/reviews --input <json_file> |
There was a problem hiding this comment.
This flips the automation contract. The existing prompt and the runner instructions require the bot to leave either a plain PR comment or a COMMENT review via gh api .../reviews, explicitly not an APPROVED/CHANGES_REQUESTED review. Telling the model to run gh pr review --approve / --request-changes will make it mutate the PR's approval state directly instead of leaving non-blocking review comments.
| - Submit via: gh api repos/PLACEHOLDER_REPO/pulls/PLACEHOLDER_PR_NUMBER/reviews --input <json_file> | |
| - Do not use: gh pr review --approve or --request-changes |
|
/review |
There was a problem hiding this comment.
Summary opinion: blocking issues found.
Findings:
.github/workflows/opencode-review-comment.ymllets any PR commenter trigger the reusable review workflow from the base-repoissue_commentcontext while forwardingsecrets: inherit. That exposes repository secrets and a write-capable token to untrusted PR code, especially on fork PRs.- Deleting
.github/workflows/pr-approve-status.ymlbreaksmasterbranch protection as currently declared in.asf.yaml, becauseNeed_2_Approvalremains a required status check and no replacement publisher is added.
Critical checkpoint conclusions:
- Goal of current task: only partially accomplished. The workflow split is implemented, but the new dispatcher is unsafe and the PR removes a required status without replacement.
- Modification size/focus: not fully focused, because it also changes branch-protection behavior by deleting
Need_2_Approval. - Concurrency: no meaningful concurrency concern in these workflows.
- Lifecycle/static initialization: not applicable.
- Configuration items: no new runtime config items, but the workflow/branch-protection contract changes incompatibly.
- Incompatible changes / parallel paths: yes. The current repository policy still requires
Need_2_Approval, and the new comment-triggered path is not equivalently protected. - Special conditional checks:
contains(github.event.comment.body, '/review')is not sufficient without actor authorization and fork checks. - Test coverage: no end-to-end workflow validation or protection-policy validation is included in the PR.
- Test results modified: none.
- Observability: failure comments exist, but they do not mitigate the blocking regressions above.
- Transaction/persistence/data-write concerns: not applicable.
- Performance: no material issue observed.
- Other issues: none beyond the blocking findings above.
| resolve-pr: | ||
| runs-on: ubuntu-latest | ||
| if: >- | ||
| github.event.issue.pull_request && |
There was a problem hiding this comment.
issue_comment runs in the base repository context, but this job only checks for a /review substring before invoking a reusable workflow with secrets: inherit. That means any outside user who can comment on a public PR can trigger a secret-backed run over untrusted head code. Please gate this on trusted author_association values and refuse fork heads (or avoid inheriting sensitive secrets entirely).
| # specific language governing permissions and limitations | ||
| # under the License. | ||
| --- | ||
| name: Need_2_Approval |
There was a problem hiding this comment.
Removing this workflow breaks the repository's current master protection rules: .asf.yaml:65 still requires the Need_2_Approval status, and there is no remaining workflow that publishes the same context. After merge, PRs will be permanently missing a required check unless the protection config is updated in the same change or this status is replaced.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
AGENTS.md:83
- The new paragraph duplicates the existing “Files in git commit should only be related…” rule earlier in this document, which makes the guidance conflicting/noisy. Please keep this rule in a single place (either remove this duplicate block or replace the earlier one).
5. The test section must honestly reflect the testing performed; do not claim tests that were not actually run
Files in a git commit should only be related to the current modification task. Environment modifications for running (for example `conf/`, `AGENTS.md`, `hooks/`) must not be `git add`ed. When delivering the final task, ensure all actual code modifications have been committed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| #### 1.3.1 Concurrency and Thread Safety (Highest Priority) | ||
|
|
||
| If it involves the judgment of concurrent scenarios, it is necessary to find the starting point of concurrency and actually understand all actually possible concurrent situations (which thread initiated what at what stage, and what concurrent operations there will be). Due to the clear program semantics, some functions of the same module are executed in stages, so concurrency is definitely not present, there should be no misjudgment. |
There was a problem hiding this comment.
This added sentence has awkward repeated wording (“actually understand all actually possible…”), which reads like a copy/edit mistake. Please rephrase to remove the repetition so the guideline is clear.
| If it involves the judgment of concurrent scenarios, it is necessary to find the starting point of concurrency and actually understand all actually possible concurrent situations (which thread initiated what at what stage, and what concurrent operations there will be). Due to the clear program semantics, some functions of the same module are executed in stages, so concurrency is definitely not present, there should be no misjudgment. | |
| If it involves the judgment of concurrent scenarios, it is necessary to find the starting point of concurrency and fully understand all possible concurrent situations (which thread initiated what at what stage, and what concurrent operations there will be). Due to the clear program semantics, some functions of the same module are executed in stages, so concurrency is definitely not present, there should be no misjudgment. |
| resolve-pr: | ||
| runs-on: ubuntu-latest | ||
| if: >- | ||
| github.event.issue.pull_request && | ||
| contains(github.event.comment.body, '/review') | ||
| outputs: |
There was a problem hiding this comment.
This workflow can be triggered by any user who comments “/review”, but it runs with write permissions and uses inherited secrets. Please restrict triggering to trusted actors (e.g., check github.event.comment.author_association for OWNER/MEMBER/COLLABORATOR or verify org/team membership) to avoid untrusted users invoking a secrets-bearing workflow.
| run: | | ||
| RUNS_JSON=$(gh api repos/${REPO}/actions/workflows/opencode-review.yml/runs --paginate -f event=pull_request -f head_sha=${HEAD_SHA}) | ||
| RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r ' | ||
| .workflow_runs | ||
| | sort_by(.created_at) | ||
| | reverse | ||
| | map(select(.head_sha != null)) |
There was a problem hiding this comment.
The rerun logic relies on gh api .../actions/workflows/.../runs query params event and especially head_sha. If head_sha is not supported/ignored, this may select and rerun the most recent workflow run from a different PR/commit. Safer approach: fetch recent runs and filter in jq by both .head_sha == HEAD_SHA and .pull_requests[].number == PR_NUMBER (you already have pr_number available) before picking the run id.
| run: | | |
| RUNS_JSON=$(gh api repos/${REPO}/actions/workflows/opencode-review.yml/runs --paginate -f event=pull_request -f head_sha=${HEAD_SHA}) | |
| RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r ' | |
| .workflow_runs | |
| | sort_by(.created_at) | |
| | reverse | |
| | map(select(.head_sha != null)) | |
| PR_NUMBER: ${{ needs.resolve-pr.outputs.pr_number }} | |
| run: | | |
| RUNS_JSON=$(gh api repos/${REPO}/actions/workflows/opencode-review.yml/runs --paginate -f event=pull_request) | |
| RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r --arg head_sha "$HEAD_SHA" --argjson pr_number "$PR_NUMBER" ' | |
| .workflow_runs | |
| | map( | |
| select( | |
| .head_sha == $head_sha and | |
| any(.pull_requests[]?; .number == $pr_number) | |
| ) | |
| ) | |
| | sort_by(.created_at) | |
| | reverse |
| name: Code Review | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
|
|
||
| permissions: | ||
| pull-requests: write | ||
| contents: read | ||
| issues: write | ||
| pull-requests: read |
There was a problem hiding this comment.
This PR removes the Need_2_Approval workflow, but .asf.yaml still lists Need_2_Approval as a required status check for master (see .asf.yaml:65). As-is, merges to master will be blocked because the required context can no longer be produced. Please either keep a workflow that reports Need_2_Approval or update the required contexts accordingly.
| name: Code Review | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
|
|
||
| permissions: | ||
| pull-requests: write | ||
| contents: read | ||
| issues: write | ||
| pull-requests: read | ||
|
|
||
| jobs: | ||
| code-review: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| if: >- | ||
| github.event.issue.pull_request && | ||
| contains(github.event.comment.body, '/review') | ||
| steps: |
There was a problem hiding this comment.
PR description says the main workflow (opencode-review.yml) “only runs actual review when called via workflow_call”, but opencode-review.yml no longer has a workflow_call trigger—the review execution lives in opencode-review-runner.yml. Please align the PR description (or workflow naming/triggers) so future maintainers understand which workflow is the callable runner vs the required-check gate.
…mment-triggered dispatch Split code review workflow into two parts: 1. Main workflow (opencode-review.yml) now triggers on PR events (opened/synchronize/reopened/ready_for_review) as a required check, but only runs actual review when called via workflow_call 2. New comment dispatcher (opencode-review-comment.yml) triggers on /review comments and invokes the main workflow with PR context This ensures the Code Review check is always
|
/review |
There was a problem hiding this comment.
Found 1 blocking issue.
-
Goal / correctness: The PR splits automated review into a
/reviewdispatcher plus apull_requeststatus workflow, but the new runner is not safe for fork PRs.opencode-review-runner.ymlchecks out the untrusted PR head and then explicitly instructs OpenCode to read and followAGENTS.mdand.claude/skills/code-review/SKILL.mdfrom that checkout whileGH_TOKENandCODE_REVIEW_ZCLLL_COPILOT_OPENCODE_KEYare available. A fork author can change those files to steer the agent into exfiltrating secrets or using the write-scoped token to mutate PR state, so the implementation does not safely accomplish its goal. No test covers this trust-boundary regression. -
Scope / focus: The workflow split itself is small, but it also changes the trust model of the review runner. That part is not sufficiently isolated because it couples privileged execution with attacker-controlled repository content.
-
Concurrency: No meaningful shared-memory concurrency issue in the YAML itself; the main risk is workflow/event sequencing, not locking.
-
Lifecycle / initialization: No special lifecycle or static-initialization concern applies here.
-
Config changes: No new Doris runtime config is added. GitHub Actions permissions and triggers do change, and the new
issue_commenttrigger now runs with inherited secrets. -
Compatibility / parallel paths: The PR replaces the old approval-status path with a new review-status path. The new path needs the same fork-safety guarantees as other privileged PR automations, but it currently lacks them.
-
Special conditions: The critical conditional path is
/reviewonissue_comment. That condition is clear enough, but the privileged execution that follows is unsafe because it trusts head contents. -
Test coverage: There are no tests or other safeguards demonstrating safe behavior for fork PRs or attacker-controlled instruction files.
-
Test results modified: None.
-
Observability: Failure logging is adequate for operational errors, but it would not help after a prompt-injection-driven misuse of the token.
-
Transaction / persistence: Not applicable.
-
Data writes / modifications: Not applicable to Doris data paths, but the workflow does have PR-write side effects through
gh. Those writes are precisely what make the trust-boundary bug serious. -
FE/BE variable passing: Not applicable.
-
Performance: No significant performance concern compared with the security issue.
-
Other issues: None beyond the blocking trust-boundary problem above.
Summary: Requesting changes in spirit. The runner must stop consuming reviewer instructions from untrusted head content, or avoid exposing secrets/write-scoped tokens when reviewing fork-controlled files.
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ inputs.head_sha }} |
There was a problem hiding this comment.
Checking out ${{ inputs.head_sha }} here makes the rest of this job run against untrusted PR contents, but this reusable workflow is invoked from issue_comment with secrets: inherit. That is a concrete privilege-escalation path for fork PRs: the contributor can change AGENTS.md or .claude/skills/code-review/SKILL.md, and the prompt later requires OpenCode to read and follow those files from the checked-out workspace while GH_TOKEN and CODE_REVIEW_ZCLLL_COPILOT_OPENCODE_KEY are available. In that scenario the model can be steered into exfiltrating credentials or performing arbitrary PR writes. The trusted review instructions need to come from the base repository revision only, or this job must avoid exposing secrets/write permissions when operating on head-controlled content.
Split code review workflow into two parts: