feat(pr-review): add agent-canvas-automation v2.7+v2.8 with Reviews API and duplicate-review guard - #2
Merged
Merged
Conversation
Tracks the standalone OpenHands Automations tarball that powers the
github-pr-review automation on m0nklabs/cryptotrader. The v1 tarball
(oh-internal://uploads/80f203b3-0678-404a-9b60-401782a0e4b9) told the
agent NOT to use the Reviews API and posted results via the issue-comments
API, which produced single-blob reviews instead of inline review threads.
v2.7 (currently deployed) replaces the prompt template and the
result-poster. Concretely:
- Prompt mandates the GitHub Pull Request Reviews API and requires the
agent to emit a ###REVIEW_JSON### block with the comment list.
- Result-poster calls POST /repos/.../pulls/{n}/reviews with comments[].
- Path validation drops comments whose path is not in the PR diff.
- REQUEST_CHANGES is downgraded to COMMENT when the bot is the PR author.
- Re-review guard: closed (PR, label_event_id) pairs are skipped.
- MCP-direct detection via _list_existing_reviews, no double-post.
- Brace-counting JSON parser with last-marker-wins rule.
Includes the source-of-truth v2.7 main.py and a README that documents
the v1 -> v2.7 transition, the bug, the fix, and a real-world demonstration
table comparing PR 380/381 (old format) with PR 400 (new format, clean
e2e test).
Refs OpenHands#339
Co-authored-by: openhands <openhands@all-hands.dev>
…ew guard PR 400 demonstrated a race: the agent posted a review via the GitHub MCP (21:06:09), then the v2.7 script also posted from the parsed ###REVIEW_JSON### block (21:06:21), producing two reviews with identical content. The v2.7 script's MCP-direct detection only ran in the 'no JSON' path, not the 'have JSON' path. v2.8 fixes this: the script now calls _list_existing_reviews and _existing_bot_review_for_commit BEFORE deciding whether to post, regardless of whether the agent emitted a parseable JSON contract or used the GitHub MCP directly. If a m0nk111-post review already exists at the same commit_id, the script closes the state without re-posting. Verified on PR 402 (m0nklabs/cryptotrader): exactly 1 review lands, vs 2 on PR 400 with v2.7. Refs the PR 400 duplicate that the user reported. Co-authored-by: openhands <openhands@all-hands.dev>
m0nk111-post
pushed a commit
that referenced
this pull request
Jun 17, 2026
…te guard + deployment-neutral config Consolidates the work that was previously proposed as PRs #1, #2 and #3 into a single reviewable change. ## What this does 1. Steers the github-pr-review skill and the agent prompt at the GitHub Pull Request Reviews API (POST /repos/{owner}/{repo}/pulls/{n}/reviews with a comments[] array) instead of the issue-comments API. Inline threads, suggestion blocks, and one-click-apply now actually work. 2. Adds the agent-canvas-automation cron runner that watches one repo for a trigger label, forks a fresh OpenHands conversation per (PR, label_event_id), waits for the conversation to finish, parses a ###REVIEW_JSON### contract out of the final response, and posts the result as a single Pull Request Review. v2.7 and v2.8 are shipped together; the difference between them is the duplicate-review guard (see below). 3. Makes the duplicate-review guard robust: before posting a parsed payload, the script queries GitHub for an existing review by the bot user at the same commit_id. If one is found, the script closes the state without re-posting — that fixes the race where the agent posts via the GitHub MCP and the script also posts from the parsed JSON, producing two reviews with identical content. 4. Removes every hardcoded reference to the original test deployment from this repo. The cron runner is generic now: REPO is an empty string with a TODO pointing at /pr-reviewer:setup, the bot-user check is resolved at runtime via GET /user with the same token that posts the review, and the SKILL.md / README references are deployment-neutral. ## Files M plugins/pr-review/scripts/prompt.py M skills/github-pr-review/SKILL.md M skills/index.js A plugins/pr-review/agent-canvas-automation/README.md A plugins/pr-review/agent-canvas-automation/v2/README.md A plugins/pr-review/agent-canvas-automation/v2.7/main.py A plugins/pr-review/agent-canvas-automation/v2.8/main.py ## Behavioural impact - New env vars: none. - One extra GET /user per cron tick (cached thereafter). That's well within the 5000/hr PAT budget. - Until REPO is filled in by /pr-reviewer:setup, _verify_token_and_repo will fail with a 404 against an empty path — fail-fast by design. ## Verification - python3 -c 'import ast; ast.parse(...)' on v2.7 + v2.8 main.py -> OK - grep -rnE 'm0nklabs|cryptotrader|m0nk111-post|pull/379|PR 379|PR OpenHands#379' on the whole tree -> zero hits (the previous test-deployment literal that used to live in REPO = ... is gone; REPO is now empty). - node scripts/build-skills-catalog.mjs regenerated skills/index.js cleanly. Co-authored-by: openhands <openhands@all-hands.dev>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
plugins/pr-review/agent-canvas-automation/to the fork. This is thesource-of-truth copy of the standalone OpenHands Automations tarball
that powers the
github-pr-reviewautomation onm0nklabs/cryptotrader— the tarball uploaded as
oh-internal://uploads/6f316eb4-7145-494b-957f-10f80af8b489and attached to automation
9581078a-7fce-4157-8fb1-04acfd4e718a.The branch contains two commits that together form the v1 → v2.8 fix:
644f143— v2.7: introduces the new prompt template (mandates theGitHub Pull Request Reviews API, requires the
###REVIEW_JSON###JSONcontract), the Reviews-API result-poster, path validation, the
REQUEST_CHANGES → COMMENTdowngrade for PR authors, there-review guard, the MCP-direct detection, and the brace-counting
JSON parser with "last-marker-wins" rule.
9252f85— v2.8: adds a duplicate-review guard in both the"no JSON" and "have JSON" paths, so that when the agent posts a
review via the GitHub MCP AND the script also posts from the parsed
JSON, only one review lands on the PR.
Why
The agent-canvas automation does not use the upstream
plugins/pr-reviewaction directly — it has its own tarball that wasuploaded before the upstream plugin reached a usable state, and the
tarball was never updated. The user reported on 2026-06-16 that the
bot was still posting single-blob reviews instead of inline review
threads. This was traced to two bugs in the v1 tarball:
Reviews API ("Do not modify files, push commits, approve via the GitHub
API, or request changes via the review API; only produce the final
comment text").
POST /repos/.../issues/{n}/comments(theissue-comments API) instead of
POST /repos/.../pulls/{n}/reviews.The v1 → v2.8 fix replaces the prompt template and the result-poster.
The v2.7 → v2.8 step adds the duplicate-review guard, which the user
triggered by reporting two reviews on PR 400. Both reviews were in the
new format (priority labels, inline threads, suggestion blocks) — the
"old" one was actually the agent's MCP-direct post at 21:06:09, and the
"new" one was the script's API post at 21:06:21 from the parsed
###REVIEW_JSON###. The v2.7 script's MCP-detection only ran in the"no JSON" path, so it didn't catch this race. v2.8 fixes it.
What's in this PR
plugins/pr-review/agent-canvas-automation/README.mdplugins/pr-review/agent-canvas-automation/v2/README.mdplugins/pr-review/agent-canvas-automation/v2.7/main.pyplugins/pr-review/agent-canvas-automation/v2.8/main.pytar -czfof this file.Real-world demonstration
What's NOT in this PR (deliberately)
separate problem in either the agent-server's WebSocket event
emission or the agent-canvas frontend's rendering (no
deduplicatein the bundled JS). It is not in the pr-review script and is not in
scope here. The v2.8 script does everything right; if you still see
the agent-canvas UI showing the same reply twice, that bug is in the
agent-server or agent-canvas frontend, not in the script.
PID 1986310, inpts/2since 11:25) is an idlebackground process from earlier in the day; it has no active TCP
connection to the agent-server (verified via
ss -tnp). It is notthe source of any "double" behavior.
How to apply this locally
Why this isn't upstreamed
OpenHands/extensionsis the public extensions registry. Theagent-canvas-automation is specific to one user's agent-canvas
deployment (a single OpenHands Automations instance on one host, with
one cron job and one automation ID). It is not general-purpose plugin
code — it's the wiring that talks to that specific deployment. When the
upstream
plugins/pr-reviewplugin gets a deployment story that doesn'tneed a custom tarball, this whole directory can be deleted.
Versions
###REVIEW_JSON###).Related
.pthshim that points the agent-server at thisfork as the public-skills source:
/home/flip/.cache/uv/archive-v0/Gd11WEMCgEp_rZFU/lib/python3.14/site-packages/openhands_fork_extensions.{py,pth}(and the CLI twin at
/home/flip/.local/share/uv/tools/openhands/lib/python3.12/site-packages/openhands_fork_extensions.{py,pth}).agent_context.load_public_skills: truein/home/flip/.openhands/settings.json.Co-authored-by
Generated by an AI agent (OpenHands) on behalf of @m0nk111.