Skip to content

chore: add the Claude review workflow - #139

Merged
joamag merged 1 commit into
masterfrom
feat/claude-review-workflow
Nov 6, 2026
Merged

joamag merged 1 commit into
masterfrom
feat/claude-review-workflow

Conversation

@joamag

@joamag joamag commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Closes #138.

Ports the review workflow from omni, adapted to what this repository is.

What it does

On a pull request being opened, updated, reopened or marked ready, the branch is checked out and handed to Claude together with the conventions of the repository, which answers with the findings as inline comments and a summary at the top. The comment is sticky, so a new push replaces the previous summary instead of stacking another one.

The model and the effort come from the CLAUDE_MODEL and CLAUDE_EFFORT repository variables, falling back to opus and max, so they can be changed without touching the file. Runs are serialised per pull request and the one in flight is cancelled when a new push arrives.

What was adapted, and why

The fork guard. This repository is public, unlike omni. A pull request opened from a fork receives no secrets, so the job could never authenticate and would put a red cross on every external contribution. It is therefore guarded with github.event.pull_request.head.repo.full_name == github.repository, and a fork pull request shows the job as skipped instead. The guard carries a second benefit on self-hosted runners: the code of a fork never runs on them.

The findings. The omni prompt looks for the things that go wrong in an ERP, documents and stock left inconsistent, the rounding of money, the templates that exist in two locales, the migrations. None of that applies here, so the prompt asks about the things that go wrong in a network library instead: connections and sockets left open, blocking calls reached from the event loop, buffers and parsers facing a partial or malformed message, the limits that guard them against a hostile peer, the paths a file service may serve.

The public interface. A section of its own, because netius is published to PyPI and its callers live outside this repository. A signature that gains an argument before the existing ones, a reordered parameter or a changed default breaks them silently. This is not a hypothetical: #137 inserted an argument in the middle of build_data and build_iframe, and a positional call that worked in 1.65.0 started raising AttributeError. The agents are extended from the outside too, so the protected methods are named as part of that surface.

The conventions. Pointed at what AGENTS.md and doc/typing.md actually require here: the Python 2.7 to 3.14 range, the annotations living in the .pyi stub beside the module rather than in the .py, the stub mirroring the module in source order with defaults written as ..., the CRLF line endings, the module docstring opening with the dotted name, not item in list, item == None, and the version of setup.py left alone.

The mention workflow that omni gained in the same commit is deliberately not ported, since omni removed it again in hivesolutions/omni#203.

Before it can work

CLAUDE_CODE_OAUTH_TOKEN is not among the repository secrets of omni, so it is an organisation secret. If its visibility is set to private repositories, this public repository will not see it and the step fails to authenticate. Worth confirming before merging; I could not check it myself, as listing organisation secrets needs an owner.

Verification

The file parses as YAML, with the triggers, the runner, the permissions, the three steps and the guard all reading as intended, and it was written with the CRLF line endings the other workflows in this repository use.

- Reviews every pull request against the conventions of the repository
- Guards the job to the branches of this repository, as a fork receives
  no secrets and its code may not run on the self-hosted runners
@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@joamag joamag left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@joamag joamag self-assigned this Nov 6, 2026
@joamag joamag added enhancement New feature or request p-high High priority issue labels Nov 6, 2026
@joamag
joamag marked this pull request as ready for review November 6, 2026 11:22
Copilot AI balanced review requested due to automatic review settings November 6, 2026 11:22
@joamag
joamag merged commit cf56e31 into master Nov 6, 2026
33 checks passed
@joamag
joamag deleted the feat/claude-review-workflow branch November 6, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 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-11-06T11:23:10.163024Z d49aeb5 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 Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @joamag's task in 4m 59s —— View job


Review of PR #139: the Claude review workflow

I found four problems in .github/workflows/review.yml, plus one nit. Each finding has an inline comment with a suggested fix. The PR is already merged and I can't change workflow files, so the fixes need a follow-up PR.

High: security

  • review.yml:26: the fork guard doesn't protect the self-hosted runners. For pull_request events, GitHub uses the workflow files from the PR's merge commit, not from master.
    • A fork can remove the guard, or add its own workflow with runs-on: [self-hosted, ubuntu, latest], and run its code on the persistent runner.
    • The only thing that decides is the setting for approving fork pull request workflows.
    • The PR description says the guard keeps fork code off the runners, which isn't true.
    • Suggested fix: use ubuntu-latest like main.yml and deploy.yml. Those minutes are free for public repositories.

Medium: bug

  • review.yml:4-8: merging or closing a PR doesn't cancel its review. When the action finds the PR already merged, it checks out master (branch.ts:173). This run shows it:
    • It was triggered by ready_for_review, and the PR merged before the job started.
    • The reviewer was put on claude/pr-139-… with an empty diff. I reviewed the change from the merge commit itself.
    • Suggested fix: add closed to the triggers and github.event.action != 'closed' to the job's if. The concurrency group then cancels the queued or running review.

Low

  • review.yml:40: fetch-depth: 1 breaks the diff against master. It leaves origin/master at depth 1. Once master has moved on, git diff origin/master...HEAD fails with no merge base, and git log origin/master..HEAD also lists commits already merged. Suggested fix: fetch-depth: 0.
  • review.yml:109: the summary may not stay sticky. Bash(gh pr comment:*) and the prompt's "use a top-level comment for the summary" let the model post a new comment on each push, instead of updating the sticky tracking comment. I'm only moderately sure this happens in practice. Suggested fix: drop the tool and change the wording.

Nit

  • review.yml:74-75 vs AGENTS.md:139: the prompt calls "Python 2.7 and Python 3.14" an AGENTS.md rule, but AGENTS.md:139 still says "compatible with Python 3.12". The CI matrix (main.yml:20) goes up to 3.14, so AGENTS.md is the one to update. Fix this →

Nothing else to report:

  • The YAML, the if block, the permissions, the concurrency group and the vars fallbacks all look right.
  • Both files keep their CRLF line endings.
  • The CHANGELOG.md entry is in the unreleased section.
  • The PR doesn't touch any Python code, tests or stubs, so items 1–4 of the review prompt don't apply.

github.event.pull_request.draft == false &&
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, ubuntu, latest]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security, high confidence. The head.repo.full_name guard doesn't keep fork code off these runners, so the PR description is wrong to say it does. For pull_request events, GitHub takes the workflow files from the PR's merge commit (refs/pull/N/merge), not from master. A fork can therefore delete the guard from this file, or add its own on: pull_request workflow with runs-on: [self-hosted, ubuntu, latest] and a run: step, and that will run on the runner.

  • This job is running on github-runner-resolute-11 right now, so the runner group already accepts this public repository.
  • The runner is persistent (/_work/github-runner-resolute-11/..., which is why the cleanup step exists). A fork job can leave something behind for a later job that holds CLAUDE_CODE_OAUTH_TOKEN, or holds the secrets of another repository on the same runner group.
  • What actually decides is the setting for approving fork pull request workflows (Settings → Actions → General).

The guard is still worth keeping because it avoids the red cross. To protect the runners, though, run on a GitHub-hosted runner like main.yml and deploy.yml do. Those minutes are free for public repositories, and this job needs nothing from the machine:

Suggested change
runs-on: [self-hosted, ubuntu, latest]
runs-on: ubuntu-latest

Then remove the Cleanup the hosted runner step (lines 34-35). If you keep the self-hosted runner instead, set fork approval to Require approval for all external contributors.

Fix this →

Comment on lines +4 to +8
types:
- opened
- synchronize
- ready_for_review
- reopened

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug, high confidence (this run shows it). Nothing stops a review when the PR closes. When the action finds the PR already merged or closed, it checks out the default branch instead (claude-code-action src/github/operations/branch.ts:173: PR is MERGED, creating new branch from source..., then git checkout master and checkout -b claude/pr-N-…).

That's what happened to this run:

  • It was triggered by ready_for_review.
  • The PR merged at 11:22:12, before the job reached its setup.
  • The reviewer started on claude/pr-139-20261106-1125, with git diff origin/master...HEAD empty.

A less careful run would spend up to 30 minutes of opus/max reviewing master, and might report "no findings" for a change it never saw.

The fix is to listen for closed. The closing run joins the review-<number> group, and cancel-in-progress cancels the queued or running review:

Suggested change
types:
- opened
- synchronize
- ready_for_review
- reopened
types:
- opened
- synchronize
- ready_for_review
- reopened
- closed

Then skip the job for that event by adding github.event.action != 'closed' && as the first line of the if (line 23).

Fix this →

- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review quality, fairly sure. After this depth-1 checkout, the action fetches the PR head at depth max(commits, 20) (branch.ts:189-241) and the base branch at depth 1 (restore-config.ts:310-318). Once master has moved past the point where the PR branched, origin/master is a shallow tip with no parents. Two things then break:

  • git diff origin/master...HEAD, the command the action's prompt tells the reviewer to run, fails with no merge base.
  • git log origin/master..HEAD lists up to 20 commits, including ones already merged.

The action's own fetch-depth.ts:7-17 describes this, and it leaves the history whole when the checkout isn't shallow. The reviewer can fall back to gh pr diff, but it can also review already-merged commits as if they were part of the PR. Netius has a small history, so a full fetch is cheap:

Suggested change
fetch-depth: 1
fetch-depth: 0

Fix this →

claude_args: |
--model ${{ env.CLAUDE_MODEL }}
--effort ${{ env.CLAUDE_EFFORT }}
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low, medium confidence. With track_progress: true the action runs in tag mode, where the summary goes into the tracking comment, and that comment is what use_sticky_comment reuses. Bash(gh pr comment:*) is left over from the omni setup and lets the model post a separate top-level comment:

  • GH_TOKEN is exported to the model (run.ts:191), so the command works.
  • Lines 102-103 ("use a top-level comment for the summary") invite the model to use it.

A run that does this stacks a new comment on every push, which defeats the sticky summary the PR describes. Tag mode tells the model to update its own comment, so this may rarely happen, but nothing here needs gh pr comment:

Suggested change
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr diff:*),Bash(gh pr view:*)"

Also change line 103 to say "put the summary in the tracking comment".

Fix this →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request p-high High priority issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review the pull requests automatically

2 participants