Skip to content

Commit 6bb3b69

Browse files
committed
feat(ci): two-phase update — haiku for deps, sonnet escalation for test fixes
Phase 1: haiku updates deps (cheap, fast, 15 turns, 10min timeout) Phase 2: build + test to verify updates work Phase 3: if tests fail, sonnet diagnoses and fixes (25 turns, 15min) gets build/test failure logs as context Validate: allow dependency files + source/test files from fix step
1 parent 879412b commit 6bb3b69

File tree

1 file changed

+121
-16
lines changed

1 file changed

+121
-16
lines changed

.github/workflows/weekly-update.yml

Lines changed: 121 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ jobs:
6565
with:
6666
gpg-private-key: ${{ secrets.BOT_GPG_PRIVATE_KEY }}
6767

68-
- name: Run updating skill with Claude Code
69-
id: claude
70-
timeout-minutes: 15
68+
- name: Update dependencies (haiku — fast, cheap)
69+
id: update
70+
timeout-minutes: 10
7171
shell: bash
7272
env:
7373
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -89,8 +89,8 @@ jobs:
8989
9090
set +e
9191
claude --print \
92-
--model sonnet \
93-
--max-turns 25 \
92+
--model haiku \
93+
--max-turns 15 \
9494
--allowedTools "Bash(pnpm:*)" "Bash(git:*)" "Read" "Write" "Edit" "Glob" "Grep" \
9595
"$(cat <<'PROMPT'
9696
/updating
@@ -104,7 +104,7 @@ jobs:
104104
Update all dependencies to their latest versions.
105105
Create one atomic commit per dependency update with a conventional commit message.
106106
Leave all changes local — the workflow handles pushing and PR creation.
107-
Skip running builds, tests, and type checks — CI runs those separately.
107+
Do not run builds or tests — the next step handles that.
108108
</instructions>
109109
110110
<success_criteria>
@@ -124,20 +124,122 @@ jobs:
124124
echo "success=false" >> $GITHUB_OUTPUT
125125
fi
126126
127+
- name: Run tests
128+
id: tests
129+
if: steps.update.outputs.success == 'true'
130+
shell: bash
131+
run: |
132+
if [ -n "$SFW_BIN" ]; then
133+
mkdir -p /tmp/sfw-bin
134+
printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm
135+
chmod +x /tmp/sfw-bin/pnpm
136+
export PATH="/tmp/sfw-bin:$PATH"
137+
fi
138+
139+
set +e
140+
pnpm run build 2>&1 | tee build-output.log
141+
BUILD_EXIT=$?
142+
if [ "$BUILD_EXIT" -ne 0 ]; then
143+
echo "tests-passed=false" >> $GITHUB_OUTPUT
144+
exit 0
145+
fi
146+
147+
pnpm test 2>&1 | tee test-output.log
148+
TEST_EXIT=$?
149+
set -e
150+
151+
if [ "$TEST_EXIT" -eq 0 ]; then
152+
echo "tests-passed=true" >> $GITHUB_OUTPUT
153+
else
154+
echo "tests-passed=false" >> $GITHUB_OUTPUT
155+
fi
156+
157+
- name: Fix test failures (sonnet — smarter, escalated)
158+
id: claude
159+
if: steps.update.outputs.success == 'true' && steps.tests.outputs.tests-passed == 'false'
160+
timeout-minutes: 15
161+
shell: bash
162+
env:
163+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
164+
GITHUB_ACTIONS: 'true'
165+
run: |
166+
if [ -n "$SFW_BIN" ]; then
167+
mkdir -p /tmp/sfw-bin
168+
printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm
169+
chmod +x /tmp/sfw-bin/pnpm
170+
export PATH="/tmp/sfw-bin:$PATH"
171+
fi
172+
173+
# Collect failure context for the agent.
174+
FAILURE_LOG=""
175+
if [ -f build-output.log ]; then
176+
FAILURE_LOG+="Build output (last 50 lines):"$'\n'
177+
FAILURE_LOG+="$(tail -50 build-output.log)"$'\n\n'
178+
fi
179+
if [ -f test-output.log ]; then
180+
FAILURE_LOG+="Test output (last 100 lines):"$'\n'
181+
FAILURE_LOG+="$(tail -100 test-output.log)"$'\n'
182+
fi
183+
184+
set +e
185+
claude --print \
186+
--model sonnet \
187+
--max-turns 25 \
188+
--allowedTools "Bash(pnpm:*)" "Bash(git:*)" "Read" "Write" "Edit" "Glob" "Grep" \
189+
"$(cat <<PROMPT
190+
Build or tests failed after dependency updates. Fix them.
191+
192+
<failure_output>
193+
$FAILURE_LOG
194+
</failure_output>
195+
196+
<instructions>
197+
Diagnose the failures from the output above.
198+
Fix the code — do not revert the dependency updates.
199+
Run the tests again to verify your fix works.
200+
Create a commit for each fix with a conventional commit message.
201+
</instructions>
202+
PROMPT
203+
)" \
204+
2>&1 | tee -a claude-output.log
205+
CLAUDE_EXIT=${PIPESTATUS[0]}
206+
set -e
207+
208+
if [ "$CLAUDE_EXIT" -eq 0 ]; then
209+
echo "success=true" >> $GITHUB_OUTPUT
210+
else
211+
echo "success=false" >> $GITHUB_OUTPUT
212+
fi
213+
214+
- name: Set final status
215+
id: final
216+
if: always()
217+
run: |
218+
# Success if: update succeeded AND (tests passed OR fix succeeded)
219+
if [ "${{ steps.update.outputs.success }}" = "true" ]; then
220+
if [ "${{ steps.tests.outputs.tests-passed }}" = "true" ] || [ "${{ steps.claude.outputs.success }}" = "true" ]; then
221+
echo "success=true" >> $GITHUB_OUTPUT
222+
exit 0
223+
fi
224+
fi
225+
echo "success=false" >> $GITHUB_OUTPUT
226+
127227
- name: Validate changes
128228
id: validate
129-
if: steps.claude.outputs.success == 'true'
229+
if: steps.final.outputs.success == 'true'
130230
run: |
131-
# Only allow changes to dependency-related files.
231+
# Allow dependency files + source/test fixes from the fix step.
132232
UNEXPECTED=""
133233
for file in $(git diff --name-only origin/main..HEAD); do
134234
case "$file" in
135-
package.json|*/package.json|pnpm-lock.yaml|*/pnpm-lock.yaml|.npmrc|pnpm-workspace.yaml) ;;
235+
package.json|*/package.json|pnpm-lock.yaml|*/pnpm-lock.yaml) ;;
236+
.npmrc|pnpm-workspace.yaml) ;;
237+
src/*|test/*|*.ts|*.mts|*.js|*.mjs) ;;
136238
*) UNEXPECTED="$UNEXPECTED $file" ;;
137239
esac
138240
done
139241
if [ -n "$UNEXPECTED" ]; then
140-
echo "::error::Unexpected files modified by Claude:$UNEXPECTED"
242+
echo "::error::Unexpected files modified:$UNEXPECTED"
141243
echo "valid=false" >> $GITHUB_OUTPUT
142244
else
143245
echo "valid=true" >> $GITHUB_OUTPUT
@@ -153,13 +255,13 @@ jobs:
153255
fi
154256
155257
- name: Push branch
156-
if: steps.claude.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
258+
if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
157259
env:
158260
BRANCH_NAME: ${{ steps.branch.outputs.branch }}
159261
run: git push origin "$BRANCH_NAME"
160262

161263
- name: Create Pull Request
162-
if: steps.claude.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
264+
if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
163265
env:
164266
GH_TOKEN: ${{ github.token }}
165267
BRANCH_NAME: ${{ steps.branch.outputs.branch }}
@@ -188,7 +290,7 @@ jobs:
188290
--base main
189291
190292
- name: Add job summary
191-
if: steps.claude.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
293+
if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
192294
env:
193295
BRANCH_NAME: ${{ steps.branch.outputs.branch }}
194296
run: |
@@ -198,12 +300,15 @@ jobs:
198300
echo "**Branch:** \`${BRANCH_NAME}\`" >> $GITHUB_STEP_SUMMARY
199301
echo "**Commits:** ${COMMIT_COUNT}" >> $GITHUB_STEP_SUMMARY
200302
201-
- name: Upload Claude output
303+
- name: Upload logs
202304
if: always()
203305
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
204306
with:
205-
name: claude-output-${{ github.run_id }}
206-
path: claude-output.log
307+
name: weekly-update-logs-${{ github.run_id }}
308+
path: |
309+
claude-output.log
310+
build-output.log
311+
test-output.log
207312
retention-days: 7
208313

209314
- uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@6096b06b1790f411714c89c40f72aade2eeaab7c # main

0 commit comments

Comments
 (0)