Repair: letsplot for radar-basic (attempt 1) #2812
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |