Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 68 additions & 38 deletions .github/workflows/autoupdate.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Autoupdate
on:
schedule:
- cron: "0 6 * * 1"
- cron: "0 23 * * 5"
workflow_dispatch:
workflow_call:
concurrency:
Expand All @@ -16,20 +16,32 @@ env:
AUTOUPDATE_BRANCH: chore/autoupdate-${{ github.run_id }}
jobs:
update:
name: Autoupdate dependencies
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v5
with: { ref: main, token: "${{ secrets.GITHUB_TOKEN }}", fetch-depth: 0 }
- run: |
- name: Checkout repo
uses: actions/checkout@v5
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Configure git
run: |
git config user.email "siarhei@dudko.dev"
git config user.name "Siarhei Dudko"
- run: |
- name: Create autoupdate branch
run: |
git checkout -b "$AUTOUPDATE_BRANCH"
git push -u origin "$AUTOUPDATE_BRANCH"
- uses: actions/setup-node@v6
with: { node-version: 24 }
- id: autoupdate
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24

- name: Run autoupdater
id: autoupdate
continue-on-error: true
uses: siarheidudko/autoupdater@v6
with:
Expand All @@ -46,14 +58,16 @@ jobs:
debug: "true"
ignore-packages: |
@types/node
- if: steps.autoupdate.outcome == 'failure'
- name: Persist autoupdater work on failure
if: steps.autoupdate.outcome == 'failure'
run: |
if [ -n "$(git status --porcelain)" ]; then
git add -A
git commit -m "chore(deps): autoupdater partial update"
fi
git push --force-with-lease origin "HEAD:$AUTOUPDATE_BRANCH" || true
- if: steps.autoupdate.outcome == 'failure'
- name: Fallback baseline update if branch still empty vs main
if: steps.autoupdate.outcome == 'failure'
run: |
git fetch origin main
if git diff --quiet origin/main; then
Expand All @@ -65,12 +79,14 @@ jobs:
git push origin "HEAD:$AUTOUPDATE_BRANCH"
fi
fi
- env:
- name: Ensure labels exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create autoupdate --color "0e8a16" --description "Automated dependency update PRs" --force || true
gh label create needs-claude --color "d4c5f9" --description "Needs Claude GitHub App to fix" --force || true
- id: diff
- name: Detect diff vs main
id: diff
if: always()
run: |
git fetch origin main "$AUTOUPDATE_BRANCH"
Expand All @@ -79,13 +95,18 @@ jobs:
else
echo "has_diff=true" >> "$GITHUB_OUTPUT"
fi
- id: pkg
- name: Read package version from branch
id: pkg
if: steps.diff.outputs.has_diff == 'true'
run: |
VERSION=$(git show "origin/$AUTOUPDATE_BRANCH:package.json" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).version))")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- id: pr_success
if: steps.autoupdate.outcome == 'success' && steps.autoupdate.outputs.updated == 'true' && steps.diff.outputs.has_diff == 'true'
- name: Open PR (autoupdater succeeded)
id: pr_success
if: |
steps.autoupdate.outcome == 'success' &&
steps.autoupdate.outputs.updated == 'true' &&
steps.diff.outputs.has_diff == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand All @@ -95,12 +116,17 @@ jobs:
- autoupdater outcome: success
- target version: v${{ steps.pkg.outputs.version }}

PR-checks (CI workflow) must be green before merge.
PR-checks must be green before merge.
EOF
)
PR_URL=$(gh pr create --base main --head "$AUTOUPDATE_BRANCH" --title "chore(deps): autoupdate v${{ steps.pkg.outputs.version }}" --body "$BODY")
PR_URL=$(gh pr create \
--base main \
--head "$AUTOUPDATE_BRANCH" \
--title "chore(deps): autoupdate v${{ steps.pkg.outputs.version }}" \
--body "$BODY")
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
- id: pr_failure
- name: Open draft PR (autoupdater failed)
id: pr_failure
if: steps.autoupdate.outcome == 'failure' && steps.diff.outputs.has_diff == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -119,34 +145,36 @@ jobs:
npm run build
npm test

See **Actions → Claude** for progress.
Claude will leave a status comment on this PR when it finishes.
PR-checks will re-run on each new commit.
EOF
)
PR_URL=$(gh pr create --base main --head "$AUTOUPDATE_BRANCH" --title "chore(deps): autoupdate (needs claude fix)" --body "$BODY" --draft)
PR_URL=$(gh pr create \
--base main \
--head "$AUTOUPDATE_BRANCH" \
--title "chore(deps): autoupdate (needs claude fix)" \
--body "$BODY" \
--draft)
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
- if: steps.pr_success.outputs.pr_url != '' || steps.pr_failure.outputs.pr_url != ''
- name: Trigger PR checks (GITHUB_TOKEN can't auto-trigger pull_request)
if: steps.pr_success.outputs.pr_url != '' || steps.pr_failure.outputs.pr_url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run test.yml --ref "$AUTOUPDATE_BRANCH" || true
- if: steps.pr_failure.outputs.pr_url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: gh workflow run claude.yml --ref main -f branch="$AUTOUPDATE_BRANCH" -f run_url="$RUN_URL"
- if: steps.pr_failure.outputs.pr_url != ''
run: |
gh workflow run pr-checks.yml --ref "$AUTOUPDATE_BRANCH" || true
- name: Dispatch Claude to fix the failed autoupdate
if: steps.pr_failure.outputs.pr_url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ steps.pr_failure.outputs.pr_url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
COMMENT=$(cat <<EOF
Claude has been dispatched to fix this PR. See [Actions → Claude](${{ github.server_url }}/${{ github.repository }}/actions/workflows/claude.yml) for progress.

Failing autoupdate run: $RUN_URL
EOF
)
gh pr comment "$PR_URL" --body "$COMMENT"
- if: steps.pr_success.outputs.pr_url != '' || steps.pr_failure.outputs.pr_url != ''
gh workflow run claude.yml --ref main \
-f branch="$AUTOUPDATE_BRANCH" \
-f pr_url="$PR_URL" \
-f run_url="$RUN_URL"
- name: Add labels to PR
if: steps.pr_success.outputs.pr_url != '' || steps.pr_failure.outputs.pr_url != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ steps.pr_success.outputs.pr_url || steps.pr_failure.outputs.pr_url }}
Expand All @@ -156,5 +184,7 @@ jobs:
else
gh pr edit "$PR_URL" --add-label autoupdate || true
fi
- if: always() && steps.diff.outputs.has_diff != 'true'
run: git push origin --delete "$AUTOUPDATE_BRANCH" || true
- name: Cleanup branch when nothing to ship
if: always() && steps.diff.outputs.has_diff != 'true'
run: |
git push origin --delete "$AUTOUPDATE_BRANCH" || true
61 changes: 45 additions & 16 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ on:
types: [opened, assigned]
workflow_dispatch:
inputs:
branch: { required: true, type: string }
run_url: { required: false, type: string }
branch:
description: "Branch Claude should operate on (used by autoupdate flow)"
required: true
type: string
pr_url:
description: "PR URL where Claude should post a status comment"
required: false
type: string
run_url:
description: "URL of the failing autoupdate run, included in the prompt for context"
required: false
type: string
concurrency:
group: "${{ github.workflow }} @ ${{ github.event.issue.number || github.event.pull_request.number || github.event.inputs.branch }}"
cancel-in-progress: false
jobs:
claude:
name: Run Claude
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
Expand All @@ -32,52 +43,70 @@ jobs:
id-token: write
actions: read
steps:
- uses: actions/checkout@v5
- name: Checkout repo
uses: actions/checkout@v5
with:
ref: ${{ github.event.inputs.branch || github.ref }}
fetch-depth: 1
- if: github.event_name == 'workflow_dispatch'
- name: Prepare autoupdate-fix prompt
if: github.event_name == 'workflow_dispatch'
id: prep
env:
BRANCH: ${{ github.event.inputs.branch }}
PR_URL: ${{ github.event.inputs.pr_url }}
RUN_URL: ${{ github.event.inputs.run_url }}
run: |
{
echo 'prompt<<PROMPT_EOF'
cat <<EOF
The dependency autoupdater failed on branch \`$BRANCH\` (run: $RUN_URL).
PR: $PR_URL

Push commits to this branch until the following exit
with code 0 in your local working tree, observed via the Bash tool —
Run these commands and observe their exit codes via the Bash tool —
not inferred:

npm run typecheck
npm run format:check
npm run build
npm test

(This repo uses prettier + tsc — there is no separate \`lint\` script.)

Hard rules:
- Run those commands yourself before every push. Do not push
if any of them is red.
- If \`npm install\` or \`npm ci\` is needed, run it first with
\`--no-audit --no-fund\` and confirm exit 0.
- Limit edits to compatibility shims.
- Limit edits to compatibility shims (types, renamed exports,
breaking-change adjustments, eslint-config tweaks for new rule
defaults). Do NOT change product logic.
- Do NOT bump the package version.
- Mind the existing CLAUDE.md guardrails: Zod \`z.ZodType\`
collection trick, no \`.max()\` on Zod arrays in Anthropic
structured output.
- When all are green, push and stop.
- When all commands are green, push commits with your fixes
(if any). \`pr-checks\` will re-run automatically on each push.

## MANDATORY final step

When you are done — whether you pushed fixes or determined no
changes were needed — you MUST post a status comment on the PR.
Run the following with the Bash tool:

gh pr comment "$PR_URL" --body "<your status summary>"

The comment must state plainly:
- Which command(s) failed initially (or "all green on first run").
- What changes you made (or "no fix needed").
- Whether you pushed any commits, and the SHA(s) if so.

Do NOT exit without posting this comment. The maintainer relies
on it to know what happened without reading the action log.

See CLAUDE.md in the repo root.
See CLAUDE.md in the repo root for the full project conventions.
EOF
echo PROMPT_EOF
} >> "$GITHUB_OUTPUT"
- uses: anthropics/claude-code-action@v1
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: "*"
prompt: ${{ steps.prep.outputs.prompt }}
claude_args: |
--allowedTools "Edit,Write,MultiEdit,Bash(git:*),Bash(npm:*),Bash(npx:*),Bash(node:*),Bash(rm:*),Bash(mkdir:*),Bash(cat:*),Bash(ls:*),Bash(echo:*),Bash(grep:*),Bash(find:*),Bash(sed:*),Bash(awk:*),Bash(head:*),Bash(tail:*),Bash(diff:*),Bash(mv:*),Bash(cp:*),Bash(touch:*)"
--allowedTools "Edit,Write,MultiEdit,Bash(git:*),Bash(gh:*),Bash(npm:*),Bash(npx:*),Bash(node:*),Bash(rm:*),Bash(mkdir:*),Bash(cat:*),Bash(ls:*),Bash(echo:*),Bash(grep:*),Bash(find:*),Bash(sed:*),Bash(awk:*),Bash(head:*),Bash(tail:*),Bash(diff:*),Bash(mv:*),Bash(cp:*),Bash(touch:*)"
77 changes: 77 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: PR checks
on:
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 5
env:
NODE_VERSION: 24
steps:
- name: Checkout repo
uses: actions/checkout@v5
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
- name: Typecheck
run: npm run typecheck
- name: Format check
run: npm run format:check
- name: Run builder
run: npm run build
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: ${{ github.workspace }}/dist
test:
name: Test (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
needs: build
strategy:
matrix:
node-version: [22, 24]

steps:
- name: Checkout repo
uses: actions/checkout@v5
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist
path: ${{ github.workspace }}/dist
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci

- name: Run test
run: npm test
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Release
on:
workflow_run:
workflows: [CI]
workflows: [PR checks]
branches: [main]
types: [completed]
workflow_dispatch:
Expand Down
Loading