Files
PEASS-ng/.github/workflows/chack-agent-pr-triage.yml
T
Carlos PolopandClaude Opus 4.8 0a1b242fcf ci: actually auto-merge windows definition bot PRs
Dispatch PR-tests with a PAT instead of github.token: a workflow run owned
by GITHUB_TOKEN does not emit a downstream `workflow_run` event, so the
chack-agent-pr-triage auto-merge job never fired for the bot PR. Also accept
the UNSTABLE merge state (the bot PR's `action_required` pull_request run
keeps it permanently UNSTABLE) and drop the explicit CI-master_test dispatch
since the PAT squash-merge's push to master already triggers the release,
avoiding a double release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 20:08:59 +02:00

340 lines
14 KiB
YAML

name: Chack-Agent PR Triage
on:
workflow_run:
workflows: ["PR-tests"]
types: [completed]
jobs:
auto_merge_windows_definition_bot_pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
pull-requests: write
steps:
- name: Resolve and verify bot PR
id: bot_pr
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
title="chore(winpeas): update windows version vulnerability definitions"
branch="bot/update-windows-version-definitions"
expected_file="build_lists/windows_version_exploits.json"
pr_number="${PR_NUMBER}"
if [ -z "$pr_number" ] && [ -n "$HEAD_BRANCH" ]; then
pr_number="$(gh pr list --state open --head "$HEAD_BRANCH" --base master --json number --jq '.[0].number')"
fi
if [ -z "$pr_number" ]; then
echo "No pull request found for this workflow_run; skipping."
echo "should_merge=false" >> "$GITHUB_OUTPUT"
exit 0
fi
pr_json="$(gh pr view "$pr_number" --json title,baseRefName,headRefName,author,isCrossRepository,files,mergeStateStatus)"
pr_title="$(jq -r .title <<<"$pr_json")"
base_ref="$(jq -r .baseRefName <<<"$pr_json")"
head_ref="$(jq -r .headRefName <<<"$pr_json")"
author="$(jq -r .author.login <<<"$pr_json")"
is_cross_repository="$(jq -r .isCrossRepository <<<"$pr_json")"
merge_state="$(jq -r .mergeStateStatus <<<"$pr_json")"
files="$(jq -r '.files[].path' <<<"$pr_json")"
file_count="$(jq -r '.files | length' <<<"$pr_json")"
if [ "$pr_title" != "$title" ] ||
[ "$base_ref" != "master" ] ||
[ "$head_ref" != "$branch" ] ||
[ "$is_cross_repository" != "false" ] ||
[ "$file_count" != "1" ] ||
[ "$files" != "$expected_file" ]; then
echo "PR #$pr_number is not the trusted windows definitions bot PR; skipping."
echo "should_merge=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$author" != "app/github-actions" ] &&
[ "$author" != "github-actions" ] &&
[ "$author" != "github-actions[bot]" ]; then
echo "PR #$pr_number is from unexpected author $author; skipping."
echo "should_merge=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Accept UNSTABLE in addition to CLEAN/HAS_HOOKS. When the bot opens the PR, GitHub
# also queues a `pull_request` PR-tests run that sits in `action_required` (it needs
# manual approval and never runs), which keeps the PR permanently UNSTABLE. The
# meaningful tests already passed: this job only runs when the dispatched PR-tests
# workflow_run concluded `success`, and the PR identity is strictly validated above.
# We still wait out the transient UNKNOWN state and refuse hard blockers
# (DIRTY/BLOCKED/BEHIND/DRAFT).
for attempt in {1..12}; do
case "$merge_state" in
CLEAN|HAS_HOOKS|UNSTABLE)
break
;;
esac
echo "PR #$pr_number mergeStateStatus=$merge_state; waiting for GitHub to finish evaluating mergeability ($attempt/12)."
sleep 10
merge_state="$(gh pr view "$pr_number" --json mergeStateStatus --jq .mergeStateStatus)"
done
case "$merge_state" in
CLEAN|HAS_HOOKS|UNSTABLE)
;;
*)
echo "Refusing to merge PR #$pr_number because mergeStateStatus=$merge_state"
echo "should_merge=false" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
echo "should_merge=true" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
echo "title=$title" >> "$GITHUB_OUTPUT"
- name: Merge trusted bot PR
if: ${{ steps.bot_pr.outputs.should_merge == 'true' }}
env:
GH_TOKEN: ${{ secrets.CHACK_AGENT_FIXER_TOKEN }}
PR_NUMBER: ${{ steps.bot_pr.outputs.pr_number }}
COMMIT_TITLE: ${{ steps.bot_pr.outputs.title }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "CHACK_AGENT_FIXER_TOKEN is required to merge this PR and dispatch the release workflow."
exit 1
fi
for attempt in {1..6}; do
if response="$(gh api \
-X PUT \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/pulls/${PR_NUMBER}/merge" \
-f merge_method=squash \
-f commit_title="$COMMIT_TITLE")"; then
merge_sha="$(jq -r '.sha // empty' <<<"$response")"
echo "Merged trusted windows definitions bot PR #$PR_NUMBER as $merge_sha."
# The squash-merge is performed with CHACK_AGENT_FIXER_TOKEN (a PAT, not
# GITHUB_TOKEN), so the resulting push to master triggers CI-master_test via its
# `push` trigger, which builds and publishes the PEASS release. We intentionally
# do NOT also dispatch CI-master_test here, to avoid publishing two releases for
# the same merge.
exit 0
fi
echo "Merge attempt $attempt failed for PR #$PR_NUMBER; retrying."
sleep 10
done
echo "Failed to merge trusted windows definitions bot PR #$PR_NUMBER after retries."
exit 1
chack_agent_triage:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
env:
CHACK_LOGS_HTTP_URL: ${{ secrets.CHACK_LOGS_HTTP_URL }}
outputs:
should_run: ${{ steps.gate.outputs.should_run }}
pr_number: ${{ steps.gate.outputs.pr_number }}
pr_title: ${{ steps.gate.outputs.pr_title }}
pr_body: ${{ steps.gate.outputs.pr_body }}
base_ref: ${{ steps.gate.outputs.base_ref }}
head_ref: ${{ steps.gate.outputs.head_ref }}
base_sha: ${{ steps.gate.outputs.base_sha }}
head_sha: ${{ steps.gate.outputs.head_sha }}
decision: ${{ steps.parse.outputs.decision }}
message: ${{ steps.parse.outputs.message }}
steps:
- name: Resolve PR context
id: gate
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
pr_number="${PR_NUMBER}"
if [ -z "$pr_number" ] && [ -n "$HEAD_BRANCH" ]; then
pr_number="$(gh pr list --state open --head "$HEAD_BRANCH" --json number --jq '.[0].number')"
fi
if [ -z "$pr_number" ]; then
echo "No pull request found for this workflow_run; skipping."
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "pr_number=" >> "$GITHUB_OUTPUT"
exit 0
fi
author="$(gh pr view "$pr_number" --json author --jq .author.login)"
if [ "$author" != "carlospolop" ]; then
echo "PR author is $author; skipping."
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
exit 0
fi
pr_title="$(gh pr view "$pr_number" --json title --jq .title)"
pr_body="$(gh pr view "$pr_number" --json body --jq .body)"
base_ref="$(gh pr view "$pr_number" --json baseRefName --jq .baseRefName)"
head_ref="$(gh pr view "$pr_number" --json headRefName --jq .headRefName)"
base_sha="$(gh pr view "$pr_number" --json baseRefOid --jq .baseRefOid)"
head_sha="$(gh pr view "$pr_number" --json headRefOid --jq .headRefOid)"
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
echo "pr_title<<EOF" >> "$GITHUB_OUTPUT"
echo "$pr_title" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "pr_body<<EOF" >> "$GITHUB_OUTPUT"
echo "$pr_body" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
echo "head_ref=$head_ref" >> "$GITHUB_OUTPUT"
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
- name: Checkout PR merge ref
uses: actions/checkout@v5
with:
ref: refs/pull/${{ steps.gate.outputs.pr_number }}/merge
if: ${{ steps.gate.outputs.should_run == 'true' }}
- name: Pre-fetch base and head refs
if: ${{ steps.gate.outputs.should_run == 'true' }}
run: |
git fetch --no-tags origin \
${{ steps.gate.outputs.base_ref }} \
+refs/pull/${{ steps.gate.outputs.pr_number }}/head
- name: Set up Node.js for Codex
if: ${{ steps.gate.outputs.should_run == 'true' }}
uses: actions/setup-node@v5
with:
node-version: "20"
- name: Install Codex CLI
if: ${{ steps.gate.outputs.should_run == 'true' }}
run: |
npm install -g @openai/codex
codex --version
- name: Run Chack Agent
id: run_chack
if: ${{ steps.gate.outputs.should_run == 'true' }}
uses: carlospolop/chack-agent@master
with:
provider: codex
model_primary: BEST_QUALITY
max_turns: 125
main_action: peass-ng-pr-traige
sub_action: Chack-Agent PR Triage
system_prompt: |
You are Chack Agent, an elite PR reviewer for PEASS-ng.
Be conservative: merge only if changes are simple, safe, and valuable accoding to the uers give guidelines.
If in doubt, comment with clear questions or concerns.
Remember taht you are an autonomouts agent, use the exec tool to run the needed commands to list, read, analyze, modify, test...
tools_config_json: "{\"exec_enabled\": true}"
session_config_json: "{\"long_term_memory_enabled\": false}"
agent_config_json: "{\"self_critique_enabled\": false, \"require_task_steps_manager_init_first\": true}"
output_schema_file: .github/chack-agent/pr-merge-schema.json
user_prompt: |
You are reviewing PR #${{ steps.gate.outputs.pr_number }} for ${{ github.repository }}.
Decide whether to merge or comment. Merge only if all of the following are true:
- Changes are simple and safe (no DoS, no long operations, no backdoors).
- Changes follow common PEASS syntax and style without breaking anything and add useful checks or value.
- Changes simplify code or add new useful checks without breaking anything.
If you don't have any doubts, and all the previous conditions are met, decide to merge.
If you have serious doubts, choose "comment" and include your doubts or questions.
If you decide to merge, include a short rationale.
Pull request title and body:
----
${{ steps.gate.outputs.pr_title }}
${{ steps.gate.outputs.pr_body }}
Review ONLY the changes introduced by the PR:
git log --oneline ${{ steps.gate.outputs.base_sha }}...${{ steps.gate.outputs.head_sha }}
Output JSON only, following the provided schema:
.github/chack-agent/pr-merge-schema.json
codex_access_token: ${{ secrets.CODEX_ACCESS_TOKEN }}
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
- name: Parse Chack Agent decision
id: parse
if: ${{ steps.gate.outputs.should_run == 'true' }}
env:
CHACK_MESSAGE: ${{ steps.run_chack.outputs.final-message }}
run: |
python3 - <<'PY'
import json
import os
raw = (os.environ.get('CHACK_MESSAGE', '') or '').strip()
decision = 'comment'
message = 'Chack Agent did not provide details.'
try:
data = json.loads(raw or '{}')
if isinstance(data, dict):
decision = data.get('decision', 'comment')
message = data.get('message', '').strip() or message
else:
message = raw or message
except Exception:
message = raw or message
with open(os.environ['GITHUB_OUTPUT'], 'a') as handle:
handle.write(f"decision={decision}\n")
handle.write("message<<EOF\n")
handle.write(message + "\n")
handle.write("EOF\n")
PY
merge_or_comment:
runs-on: ubuntu-latest
needs: chack_agent_triage
if: ${{ github.event.workflow_run.conclusion == 'success' && needs.chack_agent_triage.outputs.should_run == 'true' && needs.chack_agent_triage.outputs.decision != '' }}
permissions:
contents: write
pull-requests: write
steps:
- name: Merge PR when approved
if: ${{ needs.chack_agent_triage.outputs.decision == 'merge' }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.chack_agent_triage.outputs.pr_number }}
run: |
gh api \
-X PUT \
-H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/pulls/${PR_NUMBER}/merge \
-f merge_method=squash \
-f commit_title="Auto-merge PR #${PR_NUMBER} (Chack Agent)"
- name: Comment with doubts
if: ${{ needs.chack_agent_triage.outputs.decision == 'comment' }}
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.chack_agent_triage.outputs.pr_number }}
CHACK_MESSAGE: ${{ needs.chack_agent_triage.outputs.message }}
with:
github-token: ${{ github.token }}
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.PR_NUMBER),
body: process.env.CHACK_MESSAGE,
});