Skip to content

Instantly share code, notes, and snippets.

@alexey-max-fedorov
Last active May 16, 2026 01:23
Show Gist options
  • Select an option

  • Save alexey-max-fedorov/a3108513bbbf7f9f8c43c900e0664767 to your computer and use it in GitHub Desktop.

Select an option

Save alexey-max-fedorov/a3108513bbbf7f9f8c43c900e0664767 to your computer and use it in GitHub Desktop.
BetterDependabot — Claude Code prompt to set up daily dependency updates with lockfile sync for any project

Paste this into Claude Code:

Install "BetterDependabot" in this repo by following the spec/instructions from
https://gist.githubusercontent.com/alexey-max-fedorov/a3108513bbbf7f9f8c43c900e0664767/raw/00500fcedd5ae2dabc09dc9de46cd9078bc721ad/betterdependabot-prompt.md

BetterDependabot — Claude Code Setup Prompt

Paste this prompt into Claude Code to set up BetterDependabot on any project. BetterDependabot is a GitHub Actions workflow that runs daily, calls each package manager's native update command per directory, and commits the result — giving you automatic dependency updates that keep lockfiles in sync (unlike GitHub's built-in Dependabot, which only updates package.json and breaks frozen-lockfile CI).


The Prompt

Set up BetterDependabot for this project.

BetterDependabot is a GitHub Actions workflow that:
- Runs daily on a cron schedule (and on workflow_dispatch)
- Calls the native update command in each package directory (pnpm update / npm update / yarn upgrade / bun update)
- Commits the lockfile changes back to the default branch

This avoids the core problem with GitHub's built-in Dependabot: it updates package.json but not the root lockfile, causing ERR_PNPM_OUTDATED_LOCKFILE in CI.

---

## Step 1 — Discover all package directories

Run this to find every package.json (excluding build artifacts and node_modules):

```bash
find . -name "package.json" \
  -not -path "*/node_modules/*" \
  -not -path "*/.next/*" \
  -not -path "*/build/*" \
  -not -path "*/dist/*" \
  -not -path "*/.plasmo/*" \
  -not -path "*/coverage/*" \
  -not -path "*/.turbo/*" \
  | sort
```

For each package.json found, read it and record:
- **Directory** — relative path from repo root, use `.` for root
- **Package manager** — check `packageManager` field first (e.g. `"pnpm@10.23.0"`), then check for lockfiles:
  - `pnpm-lock.yaml` → pnpm
  - `yarn.lock` → yarn (check `packageManager` for classic vs berry)
  - `bun.lockb` → bun
  - `package-lock.json` or nothing → npm
- **Node requirement** — read `engines.node` if present

---

## Step 2 — Determine global config

- **Node version**: use the highest major version required across all packages. Minimum is `22` (pnpm 11+ requires Node ≥22; GitHub is forcing Node 24 for Actions by June 2026).
- **Uses pnpm**: true if ANY directory uses pnpm

---

## Step 3 — Generate `.github/workflows/BetterDependabot.yml`

### ⚠️ Critical rules — these mistakes caused 100% failure across 8 real repos:

1. **Always add `permissions: contents: write`** at the workflow level. GitHub's default GITHUB_TOKEN is `contents: read` — without this, every push step gets a 403.

2. **Never hardcode `version:` in `pnpm/action-setup`** if any `package.json` has a `packageManager` field. Newer versions of the action auto-read it. Both set = `ERR_PNPM_BAD_PM_VERSION`.

3. **Use plain `git push`** for the commit step, not `ad-m/github-push-action` or similar. `actions/checkout@v4` already configures the GITHUB_TOKEN for git auth — a plain push just works.

4. **Node 22 minimum** — required for pnpm 11+ compat.

5. **Must be on the default branch** — GitHub only triggers scheduled workflows for files on the default branch.

### Template:

```yaml
name: BetterDependabot

on:
  schedule:
    - cron: '0 4 * * *'
  workflow_dispatch:

permissions:
  contents: write

jobs:
  update-dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '<NODE_VERSION>'   # 22 minimum, higher if engines requires it

      # Only include if project uses pnpm — OMIT the version: field entirely
      - uses: pnpm/action-setup@v4

      # One step per package directory — for root, omit working-directory
      - name: Update <DIR> dependencies
        # working-directory: <RELATIVE_PATH>   # omit for root
        run: <UPDATE_CMD>

      # Repeat the above step for each additional directory...

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git diff --staged --quiet || git commit -m "chore: update dependencies"
          git push origin HEAD:${{ github.ref }}
```

Update commands by package manager:
- pnpm → `pnpm update`
- npm → `npm update`
- yarn (classic) → `yarn upgrade`
- yarn (berry) → `yarn up`
- bun → `bun update`

For repos with mixed package managers (e.g. root uses pnpm, a subdirectory uses npm):
- Include the `pnpm/action-setup@v4` step (pnpm global install)
- npm is already available on the runner — just call `npm update` in that directory's step
- No conflicts

---

## Step 4 — Generate `.github/dependabot.yml`

Create or update `.github/dependabot.yml` for GitHub's native Dependabot. Keep this for GitHub Actions version updates (checkout, setup-node, etc.) since BetterDependabot doesn't handle those.

```yaml
version: 2
updates:

  # One npm entry per package directory
  - package-ecosystem: "npm"
    directory: "/"        # use "/subdir" for subdirectories
    schedule:
      interval: "daily"
    commit-message:
      prefix: "[deps]"    # customize per dir if desired

  # Always include this for Actions updates
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "[actions]"
```

---

## Step 5 — Commit both files to the default branch

```bash
git add .github/workflows/BetterDependabot.yml .github/dependabot.yml
git commit -m "chore: add BetterDependabot workflow"
git push
```

---

## Step 6 — Trigger and verify

Get the GitHub remote:
```bash
git remote get-url origin
```

Trigger a run:
```bash
gh workflow run BetterDependabot.yml --repo <owner/repo>
```

Poll until done:
```bash
while true; do
  STATUS=$(gh run list --workflow=BetterDependabot.yml --repo <owner/repo> --limit 1 --json status --jq '.[0].status')
  echo "Status: $STATUS"
  if [ "$STATUS" != "in_progress" ] && [ "$STATUS" != "queued" ]; then break; fi
  sleep 30
done
```

If it fails, get the logs:
```bash
RUN_ID=$(gh run list --workflow=BetterDependabot.yml --repo <owner/repo> --limit 1 --json databaseId --jq '.[0].databaseId')
gh run view $RUN_ID --repo <owner/repo> --log
```

Fix, commit, push, and re-trigger until it passes. Common failures and their fixes:

| Error | Fix |
|-------|-----|
| `403 Permission denied` on push | Add `permissions: contents: write` to the workflow |
| `ERR_PNPM_BAD_PM_VERSION` | Remove `version:` from `pnpm/action-setup` step |
| `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite` | Bump `node-version` to `22` (or `24`) |
| `packages field missing or empty` | Add `packages: ['.']` to `pnpm-workspace.yaml` |
| Workflow doesn't trigger on schedule | Move workflow file to the default branch |

What gets created

  • .github/workflows/BetterDependabot.yml — the daily update runner
  • .github/dependabot.yml — native Dependabot for GitHub Actions version updates only

Why not just use GitHub Dependabot for everything?

Dependabot updates package.json but not the root pnpm-lock.yaml. Any CI that runs pnpm install --frozen-lockfile (e.g. Vercel) will fail with ERR_PNPM_OUTDATED_LOCKFILE. BetterDependabot runs the actual update command so lockfiles are always in sync.

Set up BetterDependabot for this project.

BetterDependabot is a GitHub Actions workflow that:

  • Runs daily on a cron schedule (and on workflow_dispatch)
  • Calls the native update command in each package directory (pnpm update / npm update / yarn upgrade / bun update)
  • Commits the lockfile changes back to the default branch

This avoids the core problem with GitHub's built-in Dependabot: it updates package.json but not the root lockfile, causing ERR_PNPM_OUTDATED_LOCKFILE in CI.


Step 1 — Discover all package directories

Run this to find every package.json (excluding build artifacts and node_modules):

find . -name "package.json" \
  -not -path "*/node_modules/*" \
  -not -path "*/.next/*" \
  -not -path "*/build/*" \
  -not -path "*/dist/*" \
  -not -path "*/.plasmo/*" \
  -not -path "*/coverage/*" \
  -not -path "*/.turbo/*" \
  | sort

For each package.json found, read it and record:

  • Directory — relative path from repo root, use . for root
  • Package manager — check packageManager field first (e.g. "pnpm@10.23.0"), then check for lockfiles:
    • pnpm-lock.yaml → pnpm
    • yarn.lock → yarn (check packageManager for classic vs berry)
    • bun.lockb → bun
    • package-lock.json or nothing → npm
  • Node requirement — read engines.node if present

Step 2 — Determine global config

  • Node version: use the highest major version required across all packages. Minimum is 22 (pnpm 11+ requires Node ≥22; GitHub is forcing Node 24 for Actions by June 2026).
  • Uses pnpm: true if ANY directory uses pnpm

Step 3 — Generate .github/workflows/BetterDependabot.yml

⚠️ Critical rules — these mistakes caused 100% failure across 8 real repos:

  1. Always add permissions: contents: write at the workflow level. GitHub's default GITHUB_TOKEN is contents: read — without this, every push step gets a 403.

  2. Never hardcode version: in pnpm/action-setup if any package.json has a packageManager field. Newer versions of the action auto-read it. Both set = ERR_PNPM_BAD_PM_VERSION.

  3. Use plain git push for the commit step, not ad-m/github-push-action or similar. actions/checkout@v4 already configures the GITHUB_TOKEN for git auth — a plain push just works.

  4. Node 22 minimum — required for pnpm 11+ compat.

  5. Must be on the default branch — GitHub only triggers scheduled workflows for files on the default branch.

Template:

name: BetterDependabot

on:
  schedule:
    - cron: '0 4 * * *'
  workflow_dispatch:

permissions:
  contents: write

jobs:
  update-dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '<NODE_VERSION>'   # 22 minimum, higher if engines requires it

      # Only include if project uses pnpm — OMIT the version: field entirely
      - uses: pnpm/action-setup@v4

      # One step per package directory — for root, omit working-directory
      - name: Update <DIR> dependencies
        # working-directory: <RELATIVE_PATH>   # omit for root
        run: <UPDATE_CMD>

      # Repeat the above step for each additional directory...

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git diff --staged --quiet || git commit -m "chore: update dependencies"
          git push origin HEAD:${{ github.ref }}

Update commands by package manager:

  • pnpm → pnpm update
  • npm → npm update
  • yarn (classic) → yarn upgrade
  • yarn (berry) → yarn up
  • bun → bun update

For repos with mixed package managers (e.g. root uses pnpm, a subdirectory uses npm):

  • Include the pnpm/action-setup@v4 step (pnpm global install)
  • npm is already available on the runner — just call npm update in that directory's step
  • No conflicts

Step 4 — Generate .github/dependabot.yml

Create or update .github/dependabot.yml for GitHub's native Dependabot. Keep this for GitHub Actions version updates (checkout, setup-node, etc.) since BetterDependabot doesn't handle those.

version: 2
updates:

  # One npm entry per package directory
  - package-ecosystem: "npm"
    directory: "/"        # use "/subdir" for subdirectories
    schedule:
      interval: "daily"
    commit-message:
      prefix: "[deps]"    # customize per dir if desired

  # Always include this for Actions updates
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "[actions]"

Step 5 — Commit both files to the default branch

git add .github/workflows/BetterDependabot.yml .github/dependabot.yml
git commit -m "chore: add BetterDependabot workflow"
git push

Step 6 — Trigger and verify

Get the GitHub remote:

git remote get-url origin

Trigger a run:

gh workflow run BetterDependabot.yml --repo <owner/repo>

Poll until done:

while true; do
  STATUS=$(gh run list --workflow=BetterDependabot.yml --repo <owner/repo> --limit 1 --json status --jq '.[0].status')
  echo "Status: $STATUS"
  if [ "$STATUS" != "in_progress" ] && [ "$STATUS" != "queued" ]; then break; fi
  sleep 30
done

If it fails, get the logs:

RUN_ID=$(gh run list --workflow=BetterDependabot.yml --repo <owner/repo> --limit 1 --json databaseId --jq '.[0].databaseId')
gh run view $RUN_ID --repo <owner/repo> --log

Fix, commit, push, and re-trigger until it passes. Common failures and their fixes:

Error Fix
403 Permission denied on push Add permissions: contents: write to the workflow
ERR_PNPM_BAD_PM_VERSION Remove version: from pnpm/action-setup step
ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite Bump node-version to 22 (or 24)
packages field missing or empty Add packages: ['.'] to pnpm-workspace.yaml
Workflow doesn't trigger on schedule Move workflow file to the default branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment