-
Notifications
You must be signed in to change notification settings - Fork 0
188 lines (158 loc) · 7.82 KB
/
Copy pathclaude-fixer.yml
File metadata and controls
188 lines (158 loc) · 7.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
name: Claude Test Fixer
on:
workflow_run:
workflows: ["Test all (Linux)", "Test all (MacOS)"]
types: [completed]
jobs:
determine-runner:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
outputs:
runner: ${{ steps.pick.outputs.runner }}
steps:
- id: pick
run: |
if [[ "${{ github.event.workflow_run.name }}" == *"MacOS"* ]]; then
echo "runner=macos-latest" >> $GITHUB_OUTPUT
else
echo "runner=ubuntu-latest" >> $GITHUB_OUTPUT
fi
fix-on-failure:
needs: determine-runner
runs-on: ${{ needs.determine-runner.outputs.runner }}
permissions:
contents: write
pull-requests: write
actions: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_branch }}
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24.x'
cache: 'npm'
- name: Enable linger (Linux only)
if: runner.os == 'Linux'
run: loginctl enable-linger $(whoami)
- run: npm ci
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Generate fix branch name
id: branch
run: echo "name=fix/claude-auto-$(date +%s)" >> $GITHUB_OUTPUT
- name: Fetch and filter failure logs from all shards
id: fetch-logs
env:
GH_TOKEN: ${{ github.token }}
run: |
REPO="${GITHUB_REPOSITORY}"
RUN_ID="${{ github.event.workflow_run.id }}"
# Write all temp files to /tmp so the working tree stays clean for Claude's changes
gh api "repos/${REPO}/actions/runs/${RUN_ID}/jobs" --paginate > /tmp/all_jobs.json
FAILED_JOB_IDS=$(jq -r '.jobs[] | select(.conclusion == "failure") | .id' /tmp/all_jobs.json)
: > /tmp/failed_logs.txt
FAILED_TEST_FILES=""
for job_id in $FAILED_JOB_IDS; do
JOB_NAME=$(jq -r ".jobs[] | select(.id == $job_id) | .name" /tmp/all_jobs.json)
echo "=== Failed Shard: ${JOB_NAME} ===" >> /tmp/failed_logs.txt
gh api "repos/${REPO}/actions/jobs/${job_id}/logs" > /tmp/raw_log.txt 2>/dev/null || {
echo "[Could not fetch logs for job ${job_id}]" >> /tmp/failed_logs.txt
continue
}
# "Failed Tests" is always the last section in vitest output —
# take from its line to EOF so we capture all failures without a hard limit.
START_LINE=$(grep -n "Failed Tests" /tmp/raw_log.txt | tail -1 | cut -d: -f1)
if [ -n "$START_LINE" ]; then
CONTEXT_START=$((START_LINE > 5 ? START_LINE - 5 : 1))
tail -n +"$CONTEXT_START" /tmp/raw_log.txt >> /tmp/failed_logs.txt
else
echo "[No 'Failed Tests' section found — showing last 100 lines]" >> /tmp/failed_logs.txt
tail -100 /tmp/raw_log.txt >> /tmp/failed_logs.txt
fi
echo "" >> /tmp/failed_logs.txt
NEW_FILES=$(grep " FAIL test/" /tmp/raw_log.txt | grep -o "test/[^[:space:]>]*\.test\.ts" | sort -u | tr '\n' ' ')
FAILED_TEST_FILES="${FAILED_TEST_FILES}${NEW_FILES}"
done
FAILED_TEST_FILES=$(echo "${FAILED_TEST_FILES}" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' ' | xargs)
echo "failed_test_files=${FAILED_TEST_FILES}" >> "$GITHUB_OUTPUT"
- name: Claude Fix Failed Tests
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--allowedTools Bash,Read,Edit,Write --max-turns 100"
prompt: |
The "${{ github.event.workflow_run.name }}" workflow failed on branch "${{ github.event.workflow_run.head_branch }}".
Failing test files: ${{ steps.fetch-logs.outputs.failed_test_files }}
Filtered failure logs from all failed shards are in `/tmp/failed_logs.txt`. Read it first.
Follow these steps carefully:
1. **Diagnose the root cause**: Read `/tmp/failed_logs.txt` thoroughly. Before writing a single line of code, understand WHY the tests are failing — not just the symptom. Read the relevant source files to understand the existing architecture and patterns.
2. **Implement an architecturally sound fix**: The fix must address the underlying root cause, match existing code patterns in this codebase, and introduce no workarounds or hacks. Do not mask symptoms.
3. **Verify each fix**: For each failing test file, run:
```
npm run test -- <test_file_path> --no-file-parallelism --disable-console-intercept
```
On macOS, prepend `CI=true` and ensure `/opt/homebrew/bin` is in PATH.
4. **Iterate until all tests pass**: If tests still fail after a fix, re-read the output, deepen your understanding of the root cause, and refine the fix. Do not give up and do not apply increasingly speculative patches. Each iteration must be grounded in analysis.
5. **Stop once all tests pass** — do NOT run any git commands. The workflow will handle committing and opening the PR.
additional_permissions: |
actions: read
- name: Restore git remote auth
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git
- name: Commit fixes
id: auto-commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "fix: auto-fix test failures from ${{ github.event.workflow_run.name }}"
branch: ${{ steps.branch.outputs.name }}
create_branch: true
- name: Open pull request and post failure logs
if: steps.auto-commit.outputs.changes_detected == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: pr } = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'fix: auto-fix ${{ github.event.workflow_run.name }} failures on ${{ github.event.workflow_run.head_branch }}',
body: [
'Failures detected in `${{ github.event.workflow_run.name }}` on branch `${{ github.event.workflow_run.head_branch }}`.',
'',
'This PR was automatically generated by the Claude Test Fixer workflow.',
'',
'> ⚠️ AI-generated fix. Please review carefully before merging.',
].join('\n'),
head: '${{ steps.branch.outputs.name }}',
base: '${{ github.event.workflow_run.head_branch }}',
});
const fs = require('fs');
let logs = fs.readFileSync('/tmp/failed_logs.txt', 'utf8');
if (logs.length > 60000) {
logs = logs.substring(0, 60000) + '\n... (truncated — see full logs in the failed workflow run)';
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: [
'## Test Failure Logs',
'',
'<details>',
'<summary>Click to expand failure logs from the failed CI run</summary>',
'',
'```',
logs,
'```',
'',
'</details>',
].join('\n'),
});