Files
PEASS-ng/.github/workflows/ci-master-failure-chack-agent-pr.yml
Carlos Polop 287fcc2332 f
2026-03-08 16:04:29 +01:00

352 lines
16 KiB
YAML

name: CI-master Failure Chack-Agent PR
on:
workflow_run:
workflows: ["CI-master_test"]
types: [completed]
jobs:
chack_agent_fix_master_failure:
if: >
${{ github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.head_branch == 'master' &&
!startsWith(github.event.workflow_run.head_commit.message, 'Fix CI-master failures for run #') }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: read
env:
TARGET_BRANCH: master
FIX_BRANCH: chack-agent/ci-master-fix-${{ github.event.workflow_run.id }}
CHACK_LOGS_HTTP_URL: ${{ secrets.CHACK_LOGS_HTTP_URL }}
CAN_PUSH_WORKFLOWS: ${{ secrets.CHACK_AGENT_FIXER_TOKEN != '' }}
steps:
- name: Checkout failing commit
uses: actions/checkout@v5
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
persist-credentials: true
token: ${{ secrets.CHACK_AGENT_FIXER_TOKEN || github.token }}
- name: Configure git author
run: |
git config user.name "chack-agent"
git config user.email "chack-agent@users.noreply.github.com"
- name: Create fix branch
run: git checkout -b "$FIX_BRANCH"
- name: Fetch failure summary and failed-step logs
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
run: |
failed_logs_file="$(pwd)/chack_failed_steps_logs.txt"
if gh run view "$RUN_ID" --repo "${{ github.repository }}" --log-failed > "$failed_logs_file"; then
if [ ! -s "$failed_logs_file" ]; then
echo "No failed step logs were returned by gh run view --log-failed." > "$failed_logs_file"
fi
else
echo "Failed to download failed step logs with gh run view --log-failed." > "$failed_logs_file"
fi
echo "FAILED_LOGS_PATH=$failed_logs_file" >> "$GITHUB_ENV"
gh api -H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/actions/runs/$RUN_ID/jobs \
--paginate > /tmp/jobs.json
python3 - <<'PY'
import json
import re
import subprocess
data = json.load(open('/tmp/jobs.json'))
lines = []
failure_evidence = []
failure_jobs = []
for job in data.get('jobs', []):
if job.get('conclusion') == 'failure':
job_id = job.get('id')
lines.append(f"Job: {job.get('name')} (id {job.get('id')})")
lines.append(f"URL: {job.get('html_url')}")
for step in job.get('steps', []):
if step.get('conclusion') == 'failure':
lines.append(f" Step: {step.get('name')}")
lines.append("")
failure_jobs.append((job_id, job.get('name')))
error_pattern = re.compile(r'error\s+[A-Z]+[0-9]+|: error |Build FAILED\.|##\[error\]', re.IGNORECASE)
for job_id, job_name in failure_jobs:
try:
raw_log = subprocess.check_output(
["gh", "api", f"/repos/${{ github.repository }}/actions/jobs/{job_id}/logs"],
text=True,
encoding="utf-8",
errors="replace",
)
except subprocess.CalledProcessError as exc:
failure_evidence.append(f"Job {job_name} ({job_id}): failed to download raw logs: {exc}")
continue
matches = []
for raw_line in raw_log.splitlines():
if error_pattern.search(raw_line):
line = re.sub(r"^\ufeff?", "", raw_line).strip()
matches.append(line)
failure_evidence.append(f"Job {job_name} ({job_id})")
if matches:
failure_evidence.extend(matches[:40])
else:
failure_evidence.append("No compiler/runtime error lines extracted from raw job logs.")
failure_evidence.append("")
summary = "\n".join(lines).strip() or "No failing job details found."
with open('chack_failure_summary.txt', 'w') as handle:
handle.write(summary)
evidence = "\n".join(failure_evidence).strip() or "No raw failure evidence extracted."
with open('chack_failure_evidence.txt', 'w') as handle:
handle.write(evidence)
PY
- name: Create Chack Agent prompt
env:
RUN_URL: ${{ github.event.workflow_run.html_url }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
{
echo "You are fixing a failing CI-master_test run in ${{ github.repository }}."
echo "The failing workflow run is: ${RUN_URL}"
echo "The failing commit SHA is: ${HEAD_SHA}"
echo "The target branch for the final PR is: ${TARGET_BRANCH}"
echo ""
echo "Failure summary:"
cat chack_failure_summary.txt
echo ""
echo "Extracted raw failure evidence:"
cat chack_failure_evidence.txt
echo ""
echo "Failed-step logs file absolute path (local runner): ${FAILED_LOGS_PATH}"
echo "Read that file to inspect the exact failing logs."
echo ""
echo "Please identify the cause, apply an easy, simple and minimal fix, and update files accordingly."
echo "Priority rule: if extracted failure evidence references repository source files or project files, fix those first."
echo "Only edit workflow files when the evidence points to the workflow itself (checkout/setup/permissions/event wiring) or when no repository file is implicated."
echo "Do not guess from truncated logs when exact compiler/runtime error lines are available."
echo "Workflow-file edits are allowed when required to fix the failing run."
echo "Do not edit or create any .md or .txt files or any summary file and do not modify build_lists/regexes.yaml. Don't create job or summary files of you actions."
echo "Run any fast checks you can locally (no network)."
echo "Leave the repo in a state ready to commit; changes will be committed and pushed automatically."
} > chack_prompt.txt
- name: Set up Node.js for Codex
uses: actions/setup-node@v5
with:
node-version: "20"
- name: Install Codex CLI
run: |
npm install -g @openai/codex
codex --version
- name: Run Chack Agent
id: run_chack
uses: carlospolop/chack-agent@master
with:
provider: codex
model_primary: CHEAP_BUT_QUALITY
max_turns: 125
main_action: peass-ng-master-failure
sub_action: CI-master Failure Chack-Agent PR
system_prompt: |
Diagnose the failing gh actions workflow, propose the minimal and effective safe fix, and implement it.
When the provided failure evidence names repository files, treat that as the primary root-cause signal and fix those files before considering workflow edits.
Do not make speculative workflow changes when compiler/runtime error lines point to source or project files.
Workflow-file edits are allowed when needed to fix the failure.
Never edit any .md or .txt files.
Never create or modify build_lists/regexes.yaml.
Run only fast, local checks (no network). Leave the repo ready to commit.
prompt_file: chack_prompt.txt
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}"
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
- name: Commit and push fix branch if changed
id: push_fix
env:
ORIGINAL_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
rm -f chack_failure_summary.txt chack_failure_evidence.txt chack_prompt.txt chack_failed_steps_logs.txt
pushed=false
if ! git diff --quiet; then
git add -A
if [ "$CAN_PUSH_WORKFLOWS" != "true" ]; then
# Avoid workflow-file pushes with token scopes that cannot write workflows.
git reset -- .github/workflows || true
git checkout -- .github/workflows || true
git clean -fdx -- .github/workflows || true
fi
git reset -- chack_failure_summary.txt chack_failure_evidence.txt chack_prompt.txt chack_failed_steps_logs.txt
# Never include generated regex list updates in automated fixer commits.
git reset -- build_lists/regexes.yaml || true
# Never allow the agent to commit generated linpeas artifacts.
git reset -- linpeas.sh linpeas_fat.sh || true
while IFS= read -r forbidden_file; do
git reset -- "$forbidden_file" || true
done < <(git diff --name-only --cached | grep -E '(^|/)(linpeas\.sh|linpeas_fat\.sh)$' || true)
while IFS= read -r file; do
case "$file" in
*.txt|*.md)
git reset -- "$file"
;;
esac
done < <(git diff --name-only --cached)
if [ "$CAN_PUSH_WORKFLOWS" != "true" ] && git diff --cached --name-only | grep -q '^.github/workflows/'; then
echo "Workflow-file changes are still staged; skipping push without workflows permission."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --cached --name-only | grep -Eq '(^|/)(linpeas\.sh|linpeas_fat\.sh)$'; then
echo "Forbidden generated linpeas files are still staged; skipping push."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! git diff --cached --quiet; then
git commit -m "Fix CI-master failures for run #${{ github.event.workflow_run.id }}"
fi
fi
after_head="$(git rev-parse HEAD)"
if [ "$after_head" = "$ORIGINAL_HEAD_SHA" ]; then
echo "No commit produced by Chack Agent."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$CAN_PUSH_WORKFLOWS" = "true" ]; then
echo "Sanitizing Chack commit range, preserving workflow fixes."
git diff --binary "$ORIGINAL_HEAD_SHA"..HEAD -- \
. \
':(exclude)chack_failure_summary.txt' \
':(exclude)chack_failure_evidence.txt' \
':(exclude)chack_prompt.txt' \
':(exclude)chack_failed_steps_logs.txt' \
':(exclude)build_lists/regexes.yaml' \
':(exclude)*.md' \
':(exclude)*.txt' \
':(exclude)**/*.txt' \
':(exclude)**/*.md' > /tmp/chack_sanitized.patch
else
echo "Sanitizing Chack commit range to non-workflow changes only."
git diff --binary "$ORIGINAL_HEAD_SHA"..HEAD -- \
. \
':(exclude).github/workflows/**' \
':(exclude)chack_failure_summary.txt' \
':(exclude)chack_failure_evidence.txt' \
':(exclude)chack_prompt.txt' \
':(exclude)chack_failed_steps_logs.txt' \
':(exclude)build_lists/regexes.yaml' \
':(exclude)*.md' \
':(exclude)*.txt' \
':(exclude)**/*.txt' \
':(exclude)**/*.md' > /tmp/chack_sanitized.patch
if [ ! -s /tmp/chack_sanitized.patch ]; then
echo "Only workflow-file changes were produced; skipping push."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
git reset --hard "$ORIGINAL_HEAD_SHA"
git apply --index /tmp/chack_sanitized.patch
rm -f chack_failure_summary.txt chack_failure_evidence.txt chack_prompt.txt chack_failed_steps_logs.txt
git reset -- chack_failure_summary.txt chack_failure_evidence.txt chack_prompt.txt chack_failed_steps_logs.txt || true
git reset -- linpeas.sh linpeas_fat.sh || true
while IFS= read -r forbidden_file; do
git reset -- "$forbidden_file" || true
done < <(git diff --name-only --cached | grep -E '(^|/)(linpeas\.sh|linpeas_fat\.sh)$' || true)
if git diff --cached --name-only | grep -Eq '(^|/)(linpeas\.sh|linpeas_fat\.sh)$'; then
echo "Forbidden generated linpeas files remain after sanitizing; skipping push."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --cached --quiet; then
echo "No sanitized changes left after filtering."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "Fix CI-master failures for run #${{ github.event.workflow_run.id }}"
if ! git push origin HEAD:"$FIX_BRANCH"; then
echo "Push failed (likely token workflow permission limits); skipping PR creation."
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
pushed=true
if [ "$pushed" = "true" ]; then
echo "pushed=true" >> "$GITHUB_OUTPUT"
else
echo "pushed=false" >> "$GITHUB_OUTPUT"
fi
- name: Create PR to master
if: ${{ steps.push_fix.outputs.pushed == 'true' }}
id: create_pr
env:
GH_TOKEN: ${{ secrets.CHACK_AGENT_FIXER_TOKEN || github.token }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
run: |
set +e
pr_output=$(gh pr create \
--title "Fix CI-master_test failure (run #${{ github.event.workflow_run.id }})" \
--body "Automated Chack Agent fix for failing CI-master_test run: ${RUN_URL}" \
--base "$TARGET_BRANCH" \
--head "$FIX_BRANCH" 2>&1)
rc=$?
set -e
if [ $rc -eq 0 ]; then
echo "url=$pr_output" >> "$GITHUB_OUTPUT"
echo "created=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$pr_output"
if echo "$pr_output" | grep -qi "not permitted to create or approve pull requests"; then
echo "PR creation blocked by repository Actions policy. Fix branch was pushed: $FIX_BRANCH"
echo "url=" >> "$GITHUB_OUTPUT"
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Unexpected PR creation error."
exit $rc
- name: Comment on created PR with Chack Agent result
if: ${{ steps.push_fix.outputs.pushed == 'true' && steps.create_pr.outputs.created == 'true' && steps.run_chack.outputs.final-message != '' }}
uses: actions/github-script@v7
env:
PR_URL: ${{ steps.create_pr.outputs.url }}
CHACK_MESSAGE: ${{ steps.run_chack.outputs.final-message }}
with:
github-token: ${{ github.token }}
script: |
const prUrl = process.env.PR_URL;
const match = prUrl.match(/\/pull\/(\d+)$/);
if (!match) {
core.info(`Could not parse PR number from URL: ${prUrl}`);
return;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(match[1]),
body: process.env.CHACK_MESSAGE,
});