Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 120 additions & 22 deletions .github/workflows/pr-review-by-openhands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,141 @@ permissions:
issues: write

jobs:
pr-review:
# Run on same-repo PRs via pull_request and on fork PRs via pull_request_target.
# Trigger when one of the following conditions is met:
# 1. A new non-draft PR is opened by a non-first-time contributor, OR
# 2. A draft PR is converted to ready for review by a non-first-time contributor, OR
# 3. The 'review-this' label is added, OR
# 4. openhands-agent or all-hands-bot is requested as a reviewer
# Note: FIRST_TIME_CONTRIBUTOR and NONE PRs require manual trigger via label/reviewer request.
authorize-pr-review:
if: |
(
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
github.event.pull_request.head.repo.fork == false
) ||
(
github.event_name == 'pull_request_target' &&
(
github.event.pull_request.head.repo.full_name != github.repository ||
github.event.pull_request.head.repo.fork == true ||
(
github.event.pull_request.draft == true &&
github.event.action == 'review_requested'
)
)
)
) &&
(
(github.event.action == 'opened' && github.event.pull_request.draft == false && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') ||
(github.event.action == 'ready_for_review' && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') ||
(github.event.action == 'labeled' && github.event.label.name == 'review-this') ||
(
github.event.action == 'review_requested' &&
(
github.event.requested_reviewer.login == 'openhands-agent' ||
github.event.requested_reviewer.login == 'all-hands-bot'
)
)
)
runs-on: ubuntu-24.04
outputs:
should_run: ${{ steps.evaluate.outputs.should_run }}
reason: ${{ steps.evaluate.outputs.reason }}
steps:
- name: Evaluate PR review authorization
id: evaluate
uses: actions/github-script@v7
with:
github-token: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC }}
script: |
const trustedAuthorAssociations = new Set([
'COLLABORATOR',
'MEMBER',
'OWNER',
])
const botReviewers = new Set(['openhands-agent', 'all-hands-bot'])
const org = 'OpenHands'

const action = context.payload.action
const pr = context.payload.pull_request
const labelName = context.payload.label?.name
const requestedReviewer = context.payload.requested_reviewer?.login
const sender = context.payload.sender?.login
const repoFullName = `${context.repo.owner}/${context.repo.repo}`
const isSameRepo = pr.head.repo.full_name === repoFullName
const isTrustedAuthor = trustedAuthorAssociations.has(
pr.author_association ?? '',
)
const isSupportedContext =
(context.eventName === 'pull_request' && isSameRepo) ||
(
context.eventName === 'pull_request_target' &&
(
!isSameRepo ||
(pr.draft === true && action === 'review_requested')
)
)
const isReviewThisLabel =
action === 'labeled' && labelName === 'review-this'
const isBotReviewerRequest =
action === 'review_requested' &&
botReviewers.has(requestedReviewer ?? '')
const senderCanAuthorize =
sender != null && (isReviewThisLabel || isBotReviewerRequest)

let senderIsOrgMember = false
if (senderCanAuthorize) {
try {
await github.request('GET /orgs/{org}/members/{username}', {
org,
username: sender,
})
senderIsOrgMember = true
} catch (error) {
if (error.status !== 404) {
throw error
}
}
}

let shouldRun = false
let reason = 'event_not_enabled'

if (!isSupportedContext) {
reason = 'unsupported_event_context'
} else if (
(action === 'opened' && pr.draft === false) ||
action === 'ready_for_review'
) {
shouldRun = isTrustedAuthor
reason = shouldRun
? 'trusted_author_auto_review'
: 'untrusted_pr_requires_org_member_trigger'
} else if (isReviewThisLabel) {
shouldRun = isTrustedAuthor || senderIsOrgMember
reason = shouldRun
? (isTrustedAuthor
? 'trusted_pr_review_this_label'
: 'org_member_labeled_untrusted_pr')
: 'review_this_label_added_by_non_org_member'
} else if (isBotReviewerRequest) {
shouldRun = isTrustedAuthor || senderIsOrgMember
reason = shouldRun
? (isTrustedAuthor
? 'trusted_author_requested_bot_reviewer'
: 'org_member_rerequested_review_on_untrusted_pr')
: 'untrusted_pr_reviewer_request_blocked'
}

core.setOutput('should_run', String(shouldRun))
core.setOutput('reason', reason)
core.info(
JSON.stringify(
{
action,
authorAssociation: pr.author_association,
isSameRepo,
isSupportedContext,
isTrustedAuthor,
labelName,
requestedReviewer,
sender,
senderIsOrgMember,
shouldRun,
reason,
},
null,
2,
),
)

pr-review:
needs: authorize-pr-review
# Auto-run for org-affiliated PR authors. For untrusted PRs, require an
# OpenHands org member to add 'review-this' or re-request the bot review.
if: needs.authorize-pr-review.outputs.should_run == 'true'
concurrency:
group: pr-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ return new ConversationClient(getAgentServerClientOptions()).someMethod(...);
- OpenHands repo bootstrap files live under `.openhands/`:
- `.openhands/setup.sh` installs `uv` (via `curl -LsSf https://astral.sh/uv/install.sh | sh`) if not present, installs frontend dependencies with `npm ci` when needed, creates `.env` from `.env.sample` if missing, appends `VITE_WORKING_DIR` for this repo when unset, and generates `src/i18n/declaration.ts` via `npm run make-i18n`.
- `.openhands/hooks.json` registers `.openhands/hooks/on_stop.sh` as a Stop hook so OpenHands runs the local quality gate (`npm run lint` and `npm test`) before finishing.
- GitHub PR-review automation should stay aligned with the current OpenHands repo conventions: keep the review workflow at `.github/workflows/pr-review-by-openhands.yml`, keep the companion `.github/workflows/pr-review-evaluation.yml`, auto-run on newly opened non-draft PRs and `ready_for_review` events from established contributors, still support the `review-this` label / `openhands-agent` / `all-hands-bot` reviewer triggers, use the OpenHands app LLM proxy defaults, and use the dual-trigger pattern (`pull_request` for same-repo PRs, `pull_request_target` for forks) so workflow changes can self-verify without widening fork secret exposure.
- GitHub PR-review automation should stay aligned with the current OpenHands repo conventions: keep the review workflow at `.github/workflows/pr-review-by-openhands.yml`, keep the companion `.github/workflows/pr-review-evaluation.yml`, auto-run on newly opened non-draft PRs and `ready_for_review` events from trusted/internal authors, still support the `review-this` label / `openhands-agent` / `all-hands-bot` reviewer triggers, use the OpenHands app LLM proxy defaults, and use the dual-trigger pattern (`pull_request` for same-repo PRs, `pull_request_target` for forks) so workflow changes can self-verify without widening fork secret exposure. External/untrusted PRs must only unlock the review workflow when the triggering actor is an OpenHands org member — either by adding `review-this` or by re-requesting `openhands-agent` / `all-hands-bot`.
- The repo now includes `.agents/skills/custom-codereview-guide.md`, adapted from `OpenHands/software-agent-sdk`, to force PR reviews to always leave either an APPROVE or COMMENT review instead of silently finishing with no review object.

- HeroUI rollback / migration notes:
Expand Down
Loading