dependabot-agent
v0.1.4
Published
On-demand CLI agent that reconciles npm/pnpm dependency overrides with GitHub Dependabot alerts
Downloads
682
Maintainers
Readme
Dependabot Override Agent
An on-demand CLI agent that reconciles dependency overrides against open GitHub Dependabot alerts. Works with both npm and pnpm, in single-package projects and monorepos.
What it does
- Detects your package manager from the lockfile (
pnpm-lock.yaml→ pnpm,package-lock.json→ npm), or you can set it explicitly. - Detects where overrides live:
- npm → top-level
overridesinpackage.json. - pnpm →
pnpm-workspace.yaml(workspace projects) if present, otherwisepnpm.overridesinpackage.json.
- npm → top-level
- Fetches all open npm Dependabot alerts for your repo via the GitHub API.
- Updates dependencies (range-bound by default — see Update strategy).
- Walks the full installed dependency tree and confirms each alerted package is actually present.
- Adds or updates override entries for packages that remain vulnerable, writing a major-bounded spec (
>=patched <nextMajor) so a fix never forces a breaking major bump. - Removes overrides whose vulnerability has been resolved.
- Leaves untouched any overrides for packages that don't appear in any Dependabot alert (assumed intentional).
- Reports deployment impact — whether vulnerable packages are in your production graph (deploy recommended) or dev/test only (branch push sufficient).
Requirements
- Node 20+
- npm or pnpm
- A GitHub Personal Access Token (classic or fine-grained) with
security_eventsread permission, scoped to the repo. In CI, this must be a real PAT stored as a secret — the automaticGITHUB_TOKENthat GitHub Actions injects cannot read the Dependabot alerts API under any circumstance. See CI integration below.
Install & invoke
There are three ways to run the agent. Pick the one that fits how you use it.
1. npx — no install
Good for a one-off run or CI. Downloads and runs the published binary on demand:
npx dependabot-agent --repo owner/repo --token ghp_xxx --dry-run2. Project dev dependency — recommended for a repo you maintain
Install it into the project you want to reconcile:
npm install --save-dev dependabot-agent
# or
pnpm add -D dependabot-agentA local install does not put
dependabot-agenton your shellPATH. Typingdependabot-agent …in your terminal will fail with "command not found" / "not recognized". A project-installed binary is only available throughnpx dependabot-agent …, or from an npm/pnpm script (wherenode_modules/.binis onPATH). The script approach is the nicest — see Config-driven workflow below.
3. Global install
Puts dependabot-agent on your PATH so you can call it by name from any directory:
npm install -g dependabot-agent
dependabot-agent --repo owner/repo --token ghp_xxx --dry-runUsage
The agent takes only flags — there are no subcommands. --dry-run is a flag, not a command, so dependabot-agent dry-run is an error; use dependabot-agent --dry-run.
# Dry run — see planned changes without writing anything
dependabot-agent --repo owner/repo --token ghp_xxx --dry-run
# Real run (writes overrides; then run your package manager's install)
dependabot-agent --repo owner/repo --token ghp_xxx
# Force a package manager instead of auto-detecting
dependabot-agent --repo owner/repo --package-manager npm
# Point at a specific project root
dependabot-agent --repo owner/repo --workspace-root ./packages/app
# Allow crossing major versions in the pre-check update
dependabot-agent --repo owner/repo --update-strategy latestThe GitHub token may also come from the GITHUB_TOKEN env var (recommended for CI — never commit it).
Config-driven workflow (recommended)
For day-to-day use you don't want to retype flags. Put your token in a .env file, your other options in dependabot-agent.config.json, and wrap the two runs (dry-run and apply) in package.json scripts. Then everyday use is just:
pnpm deps:dry-run # preview
pnpm deps:fix # applyStep 1 — token in .env (gitignored)
On startup the agent automatically loads a .env file from the workspace root (the directory you run in, or --workspace-root):
# .env (add to .gitignore — never commit this)
GITHUB_TOKEN=ghp_xxxReal shell/CI environment variables always take precedence over .env, so the file never clobbers a value you set explicitly. Any variable the agent understands (GITHUB_TOKEN, GITHUB_REPO, PACKAGE_MANAGER, …) can live here — but the token belongs in .env, never in the committed config file (a token key in the config is ignored with a warning).
Step 2 — everything else in dependabot-agent.config.json
Commit this file at the workspace root. It's auto-discovered — no --config flag needed:
{
"repo": "owner/repo",
"packageManager": "pnpm", // omit to auto-detect from the lockfile
"workspaceRoot": ".", // resolved relative to this file
"updateStrategy": "compatible",
"skipUpdate": false,
"discoverPackages": true, // auto-discover isolated sub-packages
"packages": [] // extra manifest dirs to always process
}Leave dryRun out of the config (or set false) so the same config serves both the preview and the apply script — the dry-run script adds --dry-run on the command line.
Step 3 — package.json scripts
{
"scripts": {
"deps:dry-run": "dependabot-agent --dry-run",
"deps:fix": "dependabot-agent"
}
}Now both invocations pull repo, packageManager, etc. from the config file and the token from .env:
pnpm deps:dry-run # or: npm run deps:dry-run
pnpm deps:fix # then run `pnpm install` to regenerate the lockfileThere's no separate "fix" command in the tool — deps:fix is just the agent with no --dry-run, which writes the override changes. Any config value can still be overridden ad-hoc on the command line because flags beat the config file, e.g.:
pnpm deps:fix --update-strategy latest # one-off: allow crossing majorsOptions
| Flag | Env fallback | Default | Description |
|---|---|---|---|
| --token <t> | GITHUB_TOKEN | — | GitHub PAT with security_events scope. Required. Never read from a config file. |
| --repo <owner/repo> | GITHUB_REPO | — | Target repository. Required. |
| --package-manager <pnpm\|npm> | PACKAGE_MANAGER | auto-detect | Override lockfile-based detection. |
| --workspace-root <path> | WORKSPACE_ROOT | cwd | Project root. |
| --update-strategy <compatible\|latest> | UPDATE_STRATEGY | compatible | See Update strategy. |
| --dry-run | DRY_RUN=true | false | Print planned changes without writing. |
| --skip-update | SKIP_UPDATE=true | false | Skip the pre-check dependency update. |
| --config <path> | — | auto-discover | Path to a JSON config file. |
Precedence: flags > environment variables > config file > defaults.
Config file reference
The Config-driven workflow above shows the common setup. This section is the full reference.
Discovery order (first found wins):
- the path passed to
--config <path>(parsed as JSON), dependabot-agent.config.jsonin the workspace root,- a
"dependabot-agent"key in the workspace rootpackage.json.
The config file must be JSON (a .js/.ts config is not supported). All keys are optional:
{
"repo": "owner/repo",
"packageManager": "pnpm", // omit to auto-detect from the lockfile
"workspaceRoot": ".", // resolved relative to the config file
"updateStrategy": "compatible",
"dryRun": false, // prefer leaving this out; pass --dry-run instead
"skipUpdate": false,
"discoverPackages": true, // auto-discover isolated sub-packages (default true)
"packages": [] // extra manifest dirs to always process
}The GitHub token is never read from a config file — a
tokenkey here is ignored with a warning. Keep secrets in.env/GITHUB_TOKEN/ CI secrets so they don't get committed.
Monorepos & isolated packages
The agent always processes the workspace root, and additionally each isolated sub-package — a directory with both its own package.json and its own lockfile (pnpm-lock.yaml / package-lock.json), e.g. a Firebase functions/ folder. These have a separate install and separate overrides, so each is reconciled as its own group (auto-detecting its package manager from its lockfile).
Plain workspace members (listed under pnpm-workspace.yaml's packages: or a package.json workspaces field) share the root install and the root override file, so they are intentionally not processed separately.
discoverPackages(defaulttrue) — set tofalseto process only the root.packages— explicitly list extra manifest directories (relative to the workspace root) to process, e.g. for an isolated package that doesn't have its own lockfile.
Update strategy
The agent runs a dependency update before reconciling, so legitimately-fixed packages drop off naturally.
compatible(default) — stay within existing semver ranges; does not cross majors automatically. pnpm:pnpm update; npm:npm update --save.latest— allow crossing majors. pnpm:pnpm update --latest; npm:npm install <alerted-pkg>@latestfor the alerted direct dependencies.
Either way, the overrides themselves are always major-bounded, so a fixed transitive dependency never silently jumps a major version. Transitive-only vulnerabilities are handled by overrides regardless of strategy.
Direct dependencies vs. overrides (npm's spec-match rule)
npm forbids an overrides entry for a package you also depend on directly unless the override spec matches the direct dependency's declared range (or references it via the "$name" syntax); otherwise npm install fails with a "conflicts with direct dependency" error. The agent sidesteps this by fixing the two cases through different mechanisms:
- Direct dependencies are fixed by the update pass above, which rewrites the declared range in
dependencies/devDependenciesin place. Once the package resolves to a patched version in-range, its alert closes and no override is written for it. - Transitive dependencies — whatever is still vulnerable after the update — are the only things that get overrides. Since they aren't direct deps, npm's spec-match rule doesn't apply.
The agent does not rewrite an override to match a direct dep, and it does not use the "$name" self-reference form.
Edge case: if a direct dependency is still vulnerable after the update — e.g. the only patched version is a major bump and you're on the default compatible strategy, so the update can't cross the major — the agent will write an override for a package that is also a direct dep, and npm install will then reject it with the conflict error. Fix it by re-running with --update-strategy latest (bumps the direct dep across the major and rewrites its declared range, so the alert closes without an override), or bump that dependency by hand.
After running
If overrides were added or changed, run your package manager's install to regenerate the lockfile:
pnpm install # or: npm installCI integration (GitHub Actions)
The built-in
GITHUB_TOKENcannot read Dependabot alerts. GitHub Actions' automatic, ephemeralGITHUB_TOKENcannot access the Dependabot alerts REST API under anypermissions:grant — includingsecurity-events: read, which only covers code scanning alerts, not Dependabot alerts. Passing it to the agent fails with:❌ Error: GitHub API error 403: {"message":"Resource not accessible by integration", ...}You need a real credential — a classic PAT with
security_eventsscope (or a fine-grained PAT / GitHub App token with the "Dependabot alerts: read" permission) — stored as a repository secret and passed to the agent's own steps. Other steps (checkout, opening a PR) can keep using the defaultGITHUB_TOKEN— least-privilege still applies everywhere except the one operation that structurally requires a PAT.
Minimal example (manual trigger, commits directly)
# .github/workflows/dependabot-overrides.yml
name: Reconcile Dependabot Overrides
on:
workflow_dispatch: # manual trigger only
jobs:
reconcile:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
# pnpm projects only:
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install # or: pnpm install
- name: Run Dependabot Override Agent
run: npx dependabot-agent --repo ${{ github.repository }}
env:
GITHUB_TOKEN: ${{ secrets.DEPENDABOT_AGENT_TOKEN }} # PAT secret — see note above
- name: Commit changes (if any)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git diff --quiet && echo "No changes" || (
git add -A &&
git commit -m "chore: reconcile dependency overrides with Dependabot alerts" &&
git push
)Recommended: scheduled, pinned dependency, opens a PR
For unattended/org use, install the agent as a pinned devDependency (via the
config-driven workflow above) instead of npx-ing an
arbitrary version on every run, and open a PR for review/CI instead of committing straight to the
branch:
# .github/workflows/dependabot-agent.yml
name: Reconcile Dependabot Overrides
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry run (print planned changes, write nothing, no PR)"
type: boolean
default: true
schedule:
- cron: "0 6 * * 1" # weekly — GitHub has no native "new Dependabot alert" trigger
permissions:
contents: write
pull-requests: write
jobs:
reconcile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # pnpm projects only
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 22
- run: pnpm install --frozen-lockfile # or: npm install
- name: Determine mode
id: mode
run: echo "apply=${{ (github.event_name != 'workflow_dispatch') || (!inputs.dry_run) }}" >> "$GITHUB_OUTPUT"
- name: Preview (dry run)
if: steps.mode.outputs.apply != 'true'
env:
GITHUB_TOKEN: ${{ secrets.DEPENDABOT_AGENT_TOKEN }}
run: pnpm deps:dry-run
- name: Apply
if: steps.mode.outputs.apply == 'true'
env:
GITHUB_TOKEN: ${{ secrets.DEPENDABOT_AGENT_TOKEN }}
run: pnpm deps:fix
- name: Sync lockfile(s) to new overrides
if: steps.mode.outputs.apply == 'true'
run: pnpm install --no-frozen-lockfile # repeat per isolated sub-package, e.g. functions/
- name: Open pull request
if: steps.mode.outputs.apply == 'true'
uses: peter-evans/create-pull-request@v8 # v7 targets a deprecated Node runtime — use v8+
with:
token: ${{ secrets.GITHUB_TOKEN }} # default token is fine for opening the PR itself
branch: dependabot-agent/reconcile-overrides
commit-message: "chore: reconcile dependency overrides with Dependabot alerts"
title: "chore: reconcile dependency overrides"
body: "Automated override reconciliation. **Merge only after CI passes** — the agent does not build or test."
labels: dependenciesGotchas
workflow_dispatchandscheduleonly activate once the workflow file exists on the repo's default branch. Pushing it to a feature branch alone won't make it dispatchable or let the schedule fire — merge it to your default branch first (once merged, you can still dispatch it against any other branch by picking that branch in the "Run workflow" dropdown, or viagh workflow run <name> --ref <branch>).- PRs opened with the default
GITHUB_TOKENdon't trigger otherpull_request-triggered workflows (GitHub's recursion guard). If your repo requires CI to pass before merge, that CI won't auto-run on the agent's PR unless the PR-creation step uses a PAT or GitHub App token instead — a human pushing an empty commit, or closing/reopening the PR, also kicks it. - If your repo uses GitHub's CodeQL "Default setup" code scanning, expect its JS/TS analyzer to
occasionally fail with
Only found JavaScript or TypeScript files that were empty or contained syntax errorson the agent's PRs — every reconciliation PR touches only manifests/lockfiles, never real source, which is exactly the shape that trips this. In testing this was non-deterministic (the identical diff against the identical cached overlay database produced both a pass and a fail in back-to-back runs), so it's safe to just re-run the check. If your repo enforces branch protection on code scanning, either allowlist this, or switch Code Scanning to "Advanced setup" and add apaths-ignorefor manifest/lockfile-only changes so the job doesn't run on these PRs at all.
Design notes
Package-manager abstraction
A PackageManager interface encapsulates every PM-specific operation (update command, installed-tree retrieval, dependent analysis, deployment-impact analysis) and normalizes their output into a shared internal tree, so the reconciliation logic is identical for npm and pnpm.
Override safety
- Overrides for packages not mentioned in any Dependabot alert are never removed automatically.
- When multiple alerts reference the same package, the highest
first_patched_versionwins. - Override specs are major-bounded (
>=X.Y.Z <nextMajor) — compatible with both npm and pnpm. - The agent trusts the alert state (open ⇒ keep the override), not the installed version, since the installed tree already reflects applied overrides.
- Overrides are only written for packages that remain vulnerable after the update pass — in practice transitive deps. Direct dependencies are fixed by bumping their declared range, so the agent doesn't collide with npm's rule that an override for a direct dep must match its spec. See Direct dependencies vs. overrides.
Orphaned overrides
For an override with no matching open alert, the agent queries the npm registry for the ranges that the latest upstream dependents declare. Only if every upstream range now requests a safe version is the override removed — otherwise it's kept as still load-bearing. Registry data is used because it is unaffected by your local overrides.
Note on YAML comments:
js-yamldoesn't round-trip comments, so hand-written comments inpnpm-workspace.yamlare lost on the first write.
Development
npm install
npm run build # compile to dist/
npm start -- --repo owner/repo --dry-run # run the compiled build
npm run dev -- --repo owner/repo --dry-run # build + run in one stepLicense
MIT © Michael Pipkin
