@@ -65,22 +65,31 @@ 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 : 30
68+ - name : Update dependencies (haiku)
69+ id : update
70+ timeout-minutes : 10
7171 env :
7272 ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
7373 GITHUB_ACTIONS : ' true'
7474 run : |
75+ if [ -n "$SFW_BIN" ]; then
76+ mkdir -p /tmp/sfw-bin
77+ printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm
78+ chmod +x /tmp/sfw-bin/pnpm
79+ export PATH="/tmp/sfw-bin:$PATH"
80+ fi
81+
7582 if [ -z "$ANTHROPIC_API_KEY" ]; then
7683 echo "ANTHROPIC_API_KEY not set - skipping automated update"
7784 echo "success=false" >> $GITHUB_OUTPUT
7885 exit 0
7986 fi
8087
8188 set +e
82- pnpm exec claude --print --dangerously-skip-permissions \
83- --model sonnet \
89+ pnpm exec claude --print \
90+ --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \
91+ --model haiku \
92+ --max-turns 15 \
8493 "$(cat <<'PROMPT'
8594 /updating
8695
@@ -103,7 +112,7 @@ jobs:
103112 </success_criteria>
104113 PROMPT
105114 )" \
106- 2>&1 | tee claude-output .log
115+ 2>&1 | tee claude-update .log
107116 CLAUDE_EXIT=${PIPESTATUS[0]}
108117 set -e
109118
@@ -113,6 +122,130 @@ jobs:
113122 echo "success=false" >> $GITHUB_OUTPUT
114123 fi
115124
125+ - name : Run tests
126+ id : tests
127+ if : steps.update.outputs.success == 'true'
128+ continue-on-error : true
129+ run : |
130+ if [ -n "$SFW_BIN" ]; then
131+ mkdir -p /tmp/sfw-bin
132+ printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm
133+ chmod +x /tmp/sfw-bin/pnpm
134+ export PATH="/tmp/sfw-bin:$PATH"
135+ fi
136+
137+ set +e
138+ pnpm build 2>&1 | tee build-output.log
139+ BUILD_EXIT=${PIPESTATUS[0]}
140+
141+ pnpm test 2>&1 | tee test-output.log
142+ TEST_EXIT=${PIPESTATUS[0]}
143+ set -e
144+
145+ if [ "$BUILD_EXIT" -eq 0 ] && [ "$TEST_EXIT" -eq 0 ]; then
146+ echo "result=pass" >> $GITHUB_OUTPUT
147+ else
148+ echo "result=fail" >> $GITHUB_OUTPUT
149+ fi
150+
151+ - name : Fix test failures (sonnet)
152+ id : claude
153+ if : steps.tests.outputs.result == 'fail'
154+ timeout-minutes : 15
155+ env :
156+ ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
157+ GITHUB_ACTIONS : ' true'
158+ run : |
159+ if [ -n "$SFW_BIN" ]; then
160+ mkdir -p /tmp/sfw-bin
161+ printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm
162+ chmod +x /tmp/sfw-bin/pnpm
163+ export PATH="/tmp/sfw-bin:$PATH"
164+ fi
165+
166+ BUILD_LOG=$(cat build-output.log 2>/dev/null || echo "No build output")
167+ TEST_LOG=$(cat test-output.log 2>/dev/null || echo "No test output")
168+
169+ set +e
170+ pnpm exec claude --print \
171+ --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \
172+ --model sonnet \
173+ --max-turns 25 \
174+ "$(cat <<PROMPT
175+ <context>
176+ You are an automated CI agent fixing test failures after a dependency update.
177+ Git is configured with GPG signing. Dependencies were already updated and committed.
178+ </context>
179+
180+ <failure_logs>
181+ BUILD OUTPUT:
182+ ${BUILD_LOG}
183+
184+ TEST OUTPUT:
185+ ${TEST_LOG}
186+ </failure_logs>
187+
188+ <instructions>
189+ The dependency updates above caused build or test failures.
190+ Analyze the failure logs, identify root causes, and fix them.
191+ Create one atomic commit per fix with a conventional commit message.
192+ Leave all changes local — the workflow handles pushing and PR creation.
193+ </instructions>
194+
195+ <success_criteria>
196+ All build and test failures are resolved.
197+ Each fix has its own commit.
198+ No uncommitted changes remain in the working tree.
199+ </success_criteria>
200+ PROMPT
201+ )" \
202+ 2>&1 | tee claude-fix.log
203+ CLAUDE_EXIT=${PIPESTATUS[0]}
204+ set -e
205+
206+ if [ "$CLAUDE_EXIT" -eq 0 ]; then
207+ echo "success=true" >> $GITHUB_OUTPUT
208+ else
209+ echo "success=false" >> $GITHUB_OUTPUT
210+ fi
211+
212+ - name : Set final status
213+ id : final
214+ if : always()
215+ env :
216+ UPDATE_SUCCESS : ${{ steps.update.outputs.success }}
217+ TESTS_RESULT : ${{ steps.tests.outputs.result }}
218+ FIX_SUCCESS : ${{ steps.claude.outputs.success }}
219+ run : |
220+ if [ "$UPDATE_SUCCESS" != "true" ]; then
221+ echo "success=false" >> $GITHUB_OUTPUT
222+ elif [ "$TESTS_RESULT" != "fail" ]; then
223+ echo "success=true" >> $GITHUB_OUTPUT
224+ elif [ "$FIX_SUCCESS" = "true" ]; then
225+ echo "success=true" >> $GITHUB_OUTPUT
226+ else
227+ echo "success=false" >> $GITHUB_OUTPUT
228+ fi
229+
230+ - name : Validate changes
231+ id : validate
232+ if : steps.final.outputs.success == 'true'
233+ run : |
234+ UNEXPECTED=""
235+ for file in $(git diff --name-only origin/main..HEAD); do
236+ case "$file" in
237+ package.json|*/package.json|pnpm-lock.yaml|*/pnpm-lock.yaml|.npmrc|pnpm-workspace.yaml) ;;
238+ src/*|test/*) ;;
239+ *) UNEXPECTED="$UNEXPECTED $file" ;;
240+ esac
241+ done
242+ if [ -n "$UNEXPECTED" ]; then
243+ echo "::error::Unexpected files modified by Claude:$UNEXPECTED"
244+ echo "valid=false" >> $GITHUB_OUTPUT
245+ else
246+ echo "valid=true" >> $GITHUB_OUTPUT
247+ fi
248+
116249 - name : Check for changes
117250 id : changes
118251 run : |
@@ -123,13 +256,13 @@ jobs:
123256 fi
124257
125258 - name : Push branch
126- if : steps.claude .outputs.success == 'true' && steps.changes.outputs.has-changes == 'true'
259+ if : steps.final .outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
127260 env :
128261 BRANCH_NAME : ${{ steps.branch.outputs.branch }}
129262 run : git push origin "$BRANCH_NAME"
130263
131264 - name : Create Pull Request
132- if : steps.claude .outputs.success == 'true' && steps.changes.outputs.has-changes == 'true'
265+ if : steps.final .outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
133266 env :
134267 GH_TOKEN : ${{ github.token }}
135268 BRANCH_NAME : ${{ steps.branch.outputs.branch }}
@@ -158,7 +291,7 @@ jobs:
158291 --base main
159292
160293 - name : Add job summary
161- if : steps.claude .outputs.success == 'true' && steps.changes.outputs.has-changes == 'true'
294+ if : steps.final .outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true'
162295 env :
163296 BRANCH_NAME : ${{ steps.branch.outputs.branch }}
164297 run : |
@@ -173,7 +306,11 @@ jobs:
173306 uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
174307 with :
175308 name : claude-output-${{ github.run_id }}
176- path : claude-output.log
309+ path : |
310+ claude-update.log
311+ claude-fix.log
312+ build-output.log
313+ test-output.log
177314 retention-days : 7
178315
179316 - uses : SocketDev/socket-registry/.github/actions/cleanup-git-signing@6096b06b1790f411714c89c40f72aade2eeaab7c # main
0 commit comments