Skip to content

Commit a333e3d

Browse files
authored
fix(ci): harden weekly-update — allowedTools, two-phase update, diff validation (#549)
* fix(ci): harden weekly-update — allowedTools, two-phase update, diff validation * fix(ci): use env vars instead of template expressions in run blocks (zizmor) * fix: standardize format/format:check scripts (oxfmt --write/--check) * fix(deps): override defu >=6.1.5 (prototype pollution CVE) * fix: move minimumReleaseAge to root level in pnpm-workspace.yaml * fix: remove defu override (blocked by 7-day age gate, will resolve after April 8)
1 parent 1f1764f commit a333e3d

File tree

3 files changed

+150
-14
lines changed

3 files changed

+150
-14
lines changed

.github/workflows/weekly-update.yml

Lines changed: 147 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"clean": "node scripts/clean.mjs",
4949
"cover": "node scripts/cover.mjs",
5050
"fix": "node scripts/lint.mjs --fix",
51-
"format": "oxfmt .",
51+
"format": "oxfmt --write .",
5252
"format:check": "oxfmt --check .",
5353
"generate-sdk": "node scripts/generate-sdk.mjs",
5454
"lint": "node scripts/lint.mjs",

pnpm-workspace.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
settings:
2-
# Wait 7 days (10080 minutes) before installing newly published packages.
3-
minimumReleaseAge: 10080
1+
# Wait 7 days (10080 minutes) before installing newly published packages.
2+
minimumReleaseAge: 10080

0 commit comments

Comments
 (0)