Skip to content

Repair: letsplot for radar-basic (attempt 1) #2812

Repair: letsplot for radar-basic (attempt 1)

Repair: letsplot for radar-basic (attempt 1) #2812

Workflow file for this run

name: "Impl: Repair"
run-name: "Repair: ${{ inputs.library }} for ${{ inputs.specification_id }} (attempt ${{ inputs.attempt }})"
# Repairs a rejected implementation based on AI feedback
# Triggered by impl-review.yml when ai-rejected label is added
on:
workflow_dispatch:
inputs:
pr_number:
description: "PR number to repair"
required: true
type: string
specification_id:
description: "The specification ID"
required: true
type: string
library:
description: "The library to repair"
required: true
type: string
attempt:
description: "Current attempt number (1, 2, 3, or 4)"
required: true
type: string
# Per-library deps now come from `pyproject.toml` `lib-{library}` extras
# (single source of truth — same change as impl-generate.yml).
concurrency:
group: impl-repair-${{ inputs.pr_number || github.ref }}
cancel-in-progress: false
jobs:
repair:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Checkout PR branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH=$(gh pr view ${{ inputs.pr_number }} --json headRefName -q '.headRefName')
echo "branch=$BRANCH" >> $GITHUB_ENV
git fetch origin "$BRANCH"
git checkout "$BRANCH"
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Install dependencies
env:
LIBRARY: ${{ inputs.library }}
run: |
uv venv .venv
source .venv/bin/activate
# Source of truth: pyproject.toml lib-${LIBRARY} extras
uv pip install -e ".[lib-${LIBRARY}]" ruff pillow pyyaml
- name: Setup Chrome for Highcharts
if: inputs.library == 'highcharts'
uses: browser-actions/setup-chrome@4f8e94349a351df0f048634f25fec36c3c91eded # v2
with:
chrome-version: stable
- name: Extract AI feedback from PR
id: feedback
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
FEEDBACK=$(gh pr view ${{ inputs.pr_number }} --json comments -q '
[.comments[] | select(.body | contains("AI Review"))] | last | .body
' 2>/dev/null || echo "")
if [ -z "$FEEDBACK" ]; then
FEEDBACK="No specific feedback available. Review the implementation against the spec."
fi
echo "$FEEDBACK" > /tmp/ai_feedback.md
- name: Remove ai-rejected label
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr edit ${{ inputs.pr_number }} --remove-label "ai-rejected" 2>/dev/null || true
- name: Run Claude Code to repair implementation
id: claude
continue-on-error: true
timeout-minutes: 45
uses: anthropics/claude-code-action@ef50f123a3a9be95b60040d042717517407c7256 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model sonnet"
allowed_bots: '*'
prompt: |
Read `prompts/workflow-prompts/impl-repair-claude.md` and follow those instructions.
Variables for this run:
- LANGUAGE: python
- LIBRARY: ${{ inputs.library }}
- SPEC_ID: ${{ inputs.specification_id }}
- ATTEMPT: ${{ inputs.attempt }}
- BRANCH: ${{ env.branch }}
- name: Retry Claude (on failure)
if: steps.claude.outcome == 'failure'
id: claude_retry
timeout-minutes: 45
uses: anthropics/claude-code-action@ef50f123a3a9be95b60040d042717517407c7256 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model sonnet"
allowed_bots: '*'
prompt: |
Read `prompts/workflow-prompts/impl-repair-claude.md` and follow those instructions.
Variables for this run:
- LANGUAGE: python
- LIBRARY: ${{ inputs.library }}
- SPEC_ID: ${{ inputs.specification_id }}
- ATTEMPT: ${{ inputs.attempt }}
- BRANCH: ${{ env.branch }}
- name: Process repaired image
if: success()
env:
SPEC_ID: ${{ inputs.specification_id }}
LIBRARY: ${{ inputs.library }}
LANGUAGE: python
run: |
IMPL_DIR="plots/${SPEC_ID}/implementations/${LANGUAGE}"
if [ ! -f "$IMPL_DIR/plot-light.png" ] || [ ! -f "$IMPL_DIR/plot-dark.png" ]; then
echo "::warning::Missing plot-{light,dark}.png after repair"
ls -la "$IMPL_DIR/" || true
exit 0
fi
source .venv/bin/activate
# Optimize each theme PNG and generate responsive variants named plot-{theme}_*.{png,webp}
for theme in light dark; do
python -m core.images process \
"$IMPL_DIR/plot-${theme}.png" \
"$IMPL_DIR/plot-${theme}.png"
python -m core.images responsive \
"$IMPL_DIR/plot-${theme}.png" \
"$IMPL_DIR/"
done
echo "::notice::Processed both themes after repair (light + dark)"
ls -la "$IMPL_DIR/"
- name: Authenticate to GCP
if: success()
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
with:
project_id: anyplot
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
- name: Set up Cloud SDK
if: success()
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3
- name: Upload repaired plot to GCS staging
if: success()
env:
SPEC_ID: ${{ inputs.specification_id }}
LIBRARY: ${{ inputs.library }}
LANGUAGE: python
run: |
IMPL_DIR="plots/${SPEC_ID}/implementations/${LANGUAGE}"
STAGING_PATH="gs://anyplot-images/staging/${SPEC_ID}/${LANGUAGE}/${LIBRARY}"
# Upload both theme PNGs + their responsive variants
if [ -f "$IMPL_DIR/plot-light.png" ] && [ -f "$IMPL_DIR/plot-dark.png" ]; then
gsutil -m -h "Cache-Control:public, max-age=604800" cp \
"$IMPL_DIR"/plot-light*.png "$IMPL_DIR"/plot-light*.webp \
"$IMPL_DIR"/plot-dark*.png "$IMPL_DIR"/plot-dark*.webp \
"${STAGING_PATH}/"
gsutil -m acl ch -u AllUsers:R "${STAGING_PATH}/plot-light*" "${STAGING_PATH}/plot-dark*" 2>/dev/null || true
fi
# Interactive libraries: upload both theme HTML files
for theme in light dark; do
if [ -f "$IMPL_DIR/plot-${theme}.html" ]; then
gsutil -h "Cache-Control:public, max-age=604800" cp "$IMPL_DIR/plot-${theme}.html" "${STAGING_PATH}/plot-${theme}.html"
gsutil acl ch -u AllUsers:R "${STAGING_PATH}/plot-${theme}.html" 2>/dev/null || true
fi
done
rm -f /tmp/gcs-key.json
- name: Post repair status
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr comment ${{ inputs.pr_number }} --body "## :wrench: Repair Attempt ${{ inputs.attempt }}/4
Applied fixes based on AI review feedback.
**Status:** Repair completed, re-triggering review...
---
:robot: *[impl-repair](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
- name: Re-trigger review
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ inputs.pr_number }}
run: |
# Use repository_dispatch as workaround for workflow_dispatch caching issue
gh api repos/${{ github.repository }}/dispatches \
-f event_type=review-pr \
-f "client_payload[pr_number]=$PR_NUM"
echo "::notice::Triggered impl-review.yml via repository_dispatch for PR #$PR_NUM"
# ========================================================================
# Failure handling: when the repair workflow itself crashes (e.g. Claude
# Code Action transient failure), restore the ai-rejected label and
# auto-retry once. After one auto-retry, fall back to manual.
# ========================================================================
- name: Handle repair failure
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ inputs.pr_number }}
SPEC_ID: ${{ inputs.specification_id }}
LIBRARY: ${{ inputs.library }}
ATTEMPT: ${{ inputs.attempt }}
run: |
# Restore ai-rejected label that was removed at start of repair so the
# PR state stays consistent (otherwise it looks "stuck approved").
gh pr edit "$PR_NUM" --add-label "ai-rejected" 2>/dev/null || true
MARKER="<!-- repair-retry:${SPEC_ID}:${LIBRARY}:attempt-${ATTEMPT} -->"
# Paginate so the marker is found even on PRs with >30 comments.
RETRY_COUNT=$(gh api --paginate "repos/${{ github.repository }}/issues/${PR_NUM}/comments?per_page=100" \
--jq "[.[] | select(.body != null and (.body | contains(\"$MARKER\")))] | length" 2>/dev/null || echo "0")
if [ "$RETRY_COUNT" -ge 1 ]; then
echo "::error::Repair attempt ${ATTEMPT} crashed twice — giving up"
gh pr comment "$PR_NUM" --body "${MARKER}
## :x: Repair Workflow Crashed (Attempt ${ATTEMPT}/4, retry exhausted)
The repair workflow itself failed twice for this attempt — likely a persistent Claude Code Action issue.
**Manual restart:**
\`\`\`
gh workflow run impl-repair.yml -f pr_number=${PR_NUM} -f specification_id=${SPEC_ID} -f library=${LIBRARY} -f attempt=${ATTEMPT}
\`\`\`
---
:robot: *[impl-repair](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
else
echo "::warning::Repair attempt ${ATTEMPT} crashed — auto-retrying once"
gh pr comment "$PR_NUM" --body "${MARKER}
## :wrench: Repair Workflow Crashed (Attempt ${ATTEMPT}/4) — Auto-Retrying
The repair workflow failed (probably a transient Claude Code Action issue). Automatically re-triggering this attempt...
---
:robot: *[impl-repair](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
gh workflow run impl-repair.yml \
-f pr_number="$PR_NUM" \
-f specification_id="$SPEC_ID" \
-f library="$LIBRARY" \
-f attempt="$ATTEMPT"
fi