Skip to content

CI failure repair

CI failure repair #145

# CI failure repair — uses .agents/ci-failure-repair.yaml to diagnose logs and
# optionally auto-apply Ruff fixes + push to the same branch (agent as code).
#
# Requires OPENAI_API_KEY. Auto-push only when the agent returns high confidence
# and failure_category is ruff_format or ruff_check (safe, deterministic fixes).
#
# IMPORTANT: This workflow must NEVER commit generated logs or diagnostic JSON
# to the branch. Those files are uploaded as GitHub Actions artifacts instead.
# This avoids polluting PRs with noise that confuses contributors and reviewers.
name: CI failure repair
on:
workflow_run:
workflows:
- CI
types:
- completed
concurrency:
group: ci-repair-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
jobs:
diagnose:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
pull-requests: read
steps:
- name: Checkout failed head
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install OA CLI + deps for applying fixes
run: |
python -m pip install --upgrade pip
pip install .
pip install ".[dev]"
- name: Fetch failed job log
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p /tmp/repair-artifacts
gh run view "${{ github.event.workflow_run.id }}" --log-failed > /tmp/repair-artifacts/failed-log.txt 2>/dev/null || \
gh run view "${{ github.event.workflow_run.id }}" --log > /tmp/repair-artifacts/failed-log.txt
head -c 120000 /tmp/repair-artifacts/failed-log.txt > /tmp/repair-artifacts/failed-log.trimmed.txt
mv /tmp/repair-artifacts/failed-log.trimmed.txt /tmp/repair-artifacts/failed-log.txt
wc -c /tmp/repair-artifacts/failed-log.txt
- name: Run OA repair agent
id: oa
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
if [ -z "$OPENAI_API_KEY" ]; then
echo "OPENAI_API_KEY not set — skipping agent run"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
LOG_CONTENT=$(jq -Rs . < /tmp/repair-artifacts/failed-log.txt)
jq -n \
--arg w "${{ github.event.workflow_run.name }}" \
--arg b "${{ github.event.workflow_run.head_branch }}" \
--arg c "Python package; CI runs ruff check, ruff format --check, mypy, pytest" \
--argjson log "$LOG_CONTENT" \
'{workflow_name:$w, branch:$b, repo_context:$c, job_log:$log}' > /tmp/repair-artifacts/repair-input.json
oa run --spec .agents/ci-failure-repair.yaml \
--task diagnose_and_remediate \
--input "$(cat /tmp/repair-artifacts/repair-input.json)" \
--quiet > /tmp/repair-artifacts/repair-output.json
echo "skip=false" >> "$GITHUB_OUTPUT"
cat /tmp/repair-artifacts/repair-output.json | head -c 4000
- name: Apply Ruff remediation and push
if: steps.oa.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if ! jq -e . /tmp/repair-artifacts/repair-output.json >/dev/null 2>&1; then
echo "No valid repair-output.json"
exit 0
fi
if jq -e '.output' /tmp/repair-artifacts/repair-output.json >/dev/null 2>&1; then
jq '.output' /tmp/repair-artifacts/repair-output.json > /tmp/repair-artifacts/repair-plan.json
else
cp /tmp/repair-artifacts/repair-output.json /tmp/repair-artifacts/repair-plan.json
fi
CATEGORY=$(jq -r '.failure_category // empty' /tmp/repair-artifacts/repair-plan.json)
CONF=$(jq -r '.confidence // empty' /tmp/repair-artifacts/repair-plan.json)
PUSH=$(jq -r '.push_to_branch // false' /tmp/repair-artifacts/repair-plan.json)
echo "category=$CATEGORY confidence=$CONF push_to_branch=$PUSH"
if [ "$PUSH" != "true" ] || [ "$CONF" != "high" ]; then
echo "Agent did not authorize auto-push; upload artifact only."
exit 0
fi
case "$CATEGORY" in
ruff_format|ruff_check)
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
ruff format . --exclude test_output/ || true
ruff check --fix . --exclude test_output/ || true
if git diff --quiet; then
echo "No changes after ruff — nothing to push"
exit 0
fi
MSG=$(jq -r '.commit_message // "chore: ci auto-fix (ruff)"' /tmp/repair-artifacts/repair-plan.json)
# Stage only tracked source files that ruff modified; logs live
# outside the workspace in /tmp so they can never be staged.
git add -u
git commit -m "$MSG"
git push origin "HEAD:${{ github.event.workflow_run.head_branch }}"
echo "Pushed remediation to ${{ github.event.workflow_run.head_branch }}"
;;
*)
echo "Category $CATEGORY not auto-applied; see ci-repair-plan artifact"
;;
esac
- name: Upload repair plan artifact
if: always() && steps.oa.outputs.skip != 'true'
uses: actions/upload-artifact@v4
with:
name: ci-repair-plan
path: /tmp/repair-artifacts/
retention-days: 14