Daily workflow
Dependencies: an agent you configure once and supervise forever.
Every real project sits on top of packages other people update weekly. Left alone, those updates pile up into risky, months-long upgrade projects. Handled well, they arrive as a steady stream of small pull requests opened by a bot, tested by your CI, and merged under your review. That bot is a dependency-update agent, and directing it uses the exact skills this whole site teaches: write the rules down, let machinery do the repetitive work, verify before trusting.
renovate.json the bot's written instructions#The mental model
Your project declares intent (roughly which versions it wants, in package.json or similar) and records resolution (exactly which versions it got, in a lockfile). A dependency bot watches the package registries, notices when something newer satisfies your intent, and opens a pull request that updates both files together. Your CI runs on that PR like on any other. You review and merge, or you teach the bot which categories it may merge itself.
The tool used across these repos is Renovate. Treat it exactly like the coding agents on the AI agents & rules page: it is tireless and consistent, it does exactly what its instructions say, and it has no judgment about what its changes mean. The instructions file is the contract; the PRs and dashboard are how you supervise.
#What the bot actually does to a repo
Concretely, on this repo (Groundwork) and its siblings, Renovate:
- Opens weekly, grouped PRs labeled
dependencies. Related bumps travel together so review is one read, not twenty. - Maintains an always-current Dependency Dashboard issue: every pending update, what is waiting on schedule, what is held by the cooldown. Ticking a checkbox forces a PR ahead of schedule.
- Opens security PRs immediately, bypassing the weekly schedule, and merges them itself once CI is green. A known-vulnerable version should not wait for Monday.
- Updates things that are not npm packages at all: GitHub Actions pinned to commit SHAs (it moves the pin and the human-readable version comment together) and Docker base-image digests.
- Embeds each package's release notes in the PR body, so the review starts with what changed, not with hunting for a changelog.
The bot bumps versions; it never adopts features. If a release ships a new API that would simplify your code, that judgment call is yours (or a task for a coding agent you direct at it). Reading the release notes in each PR is where those opportunities surface, which is a strong reason to keep updates small and regular instead of letting them pile up.
#The cooldown: patience as a security control
Supply-chain attacks on package registries follow a pattern: a malicious release appears (a hijacked maintainer account, a typosquat, a compromised build), and the ecosystem usually catches and yanks it within days. A cooldown (Renovate's minimumReleaseAge) says: never propose a release younger than N days. These repos use five days. You give up a little freshness and buy a real defense: by the time a version reaches a PR, the world has had a week to notice if it was poisoned.
Package managers are adopting the same idea at install time (pnpm's minimumReleaseAge refuses too-young versions during resolution), so the floor holds even when a human runs the install by hand. Security fixes get an explicit escape hatch, because for a known vulnerability the risk calculus reverses.
Be precise about what the floor covers, because it is not one floor — it is one per tool, and they do not automatically agree. Renovate applies the five-day cooldown to a repository's dependencies (packages, GitHub Action SHAs, container digests). pnpm applies its own minimumReleaseAge at install time. And on the machine, mise applies the same five-day floor to runtime upgrades — Groundwork's update-all runs mise upgrade --minimum-release-age 5d, which filters floating versions like lts and latest while leaving an explicitly pinned version exempt (that exemption is the escape hatch for a security fix that must land now).
Homebrew is the honest gap: it has no minimumReleaseAge equivalent, so a formula or cask upgrade takes whatever is published. What it does offer is --require-sha, which refuses to upgrade a cask that ships no checksum, and Groundwork's update-all uses it. Two different controls, and it pays to keep them straight: an age floor asks "has the world had time to notice this release is poisoned?", while a checksum asks "is this the artifact the formula author signed off on?" Neither substitutes for the other.
This is also a good lesson in checking a claim before acting on it. It is widely repeated that "the fast-channel AI CLIs have no checksum," because Homebrew's version :latest casks are indeed required to declare sha256 :no_check. But that is not what these casks are: claude-code@latest is a faster release channel — an ordinary versioned, checksummed cask — not a :latest cask. Verifying that with brew info --json=v2 --cask took one command and changed the design; assuming it would have added a whole subsystem to guard a risk that was not there.
So the accurate picture, and the transferable habit — know which of your paths are gated by what, and never assume a floor you have not configured:
| Repo dependencies (Renovate) | 5-day age floor; integrity via the registry/ecosystem |
| mise runtimes | 5-day age floor (--minimum-release-age 5d); pinned versions exempt, which is the security escape hatch. If a mise is too old to enforce the floor, Groundwork skips the runtime stage rather than upgrading without it |
| Homebrew casks | Checksum required (--require-sha, at install and upgrade); no age floor |
| Homebrew formulae | Homebrew's own formula/bottle integrity; no age floor. --require-sha is cask-specific — it does not speak to formulae |
One named exception, stated rather than hidden: Google Chrome ships sha256 :no_check upstream, because Google's own updater owns the binary. It therefore cannot satisfy --require-sha, so Groundwork installs it deliberately and out loud, outside the Brewfile — which is exactly what lets every cask in the Brewfile be checksum-verified. scripts/audit-brew-casks enforces that: any other unchecked cask is a policy violation, not a judgment call.
And a manually-run updater is not a substitute for any of this. Running it yourself controls the timing and prevents an unattended rollout — but nobody recognizes a malicious binary by watching upgrade output scroll past.
#One policy, many repos
Once two repos both run the bot, their configs drift: one repo gets a better cooldown, the other keeps the old value, and nobody decided that. The fix is the same one you use for agent rules: define shared policy once, in one place, and have each repo reference it. Renovate supports this with shareable presets. These repos extend a small public preset repo, renovate-config, which holds only the policy that must stay identical everywhere: schedule, cooldown, PR limits, labels, rebase behavior, and the security-PR rules. Anything repo-specific (which packages need human review, which groups make sense) stays in that repo's own renovate.json.
Copy, don't depend. That preset repo is MIT-licensed and deliberately public as a worked example: read it, fork it, copy the files into your own automation repo. Don't extends someone else's living policy, including this one; it changes without notice, and your dependency policy should answer to you.
#Hosted or self-hosted: a real choice
Renovate runs two ways, and both are legitimate:
- The hosted Mend app: two clicks to install, zero credentials to manage. Right for a first project and for anyone who never wants to read a job log. One trap: in the onboarding wizard the default mode can be "Scan Only," which runs silently and opens nothing. Pick "Scan and Alert" (Interactive), or the bot looks installed but broken.
- A self-hosted runner: a small GitHub Actions workflow in an automation repo that runs Renovate on a schedule with a token you control. You gain what hosted hides: every run is a log you can open, grep, and re-dispatch with debug logging. These repos moved to self-hosted after three separate "why is the bot ignoring me?" mysteries whose answers all lived in logs only the hosted portal could see.
The trade is the same one this site keeps pointing at: convenience versus observability. Start hosted if you're new; move to self-hosted the first time you can't answer "what is the bot doing?" from where you sit. The config file is identical either way, so switching is an executor swap, not a migration.
#Supervising the bot
The human stays the senior partner. In practice that means the config encodes different trust levels for different blast radii:
- Majors are held for approval. Framework majors (React, Next.js, TypeScript) appear on the dashboard as approval requests, not open PRs. A major upgrade is deliberate work with its own plan, never something absorbed into a routine merge.
- High-blast-radius packages always get human review, even for patches. Deployment engines and infrastructure adapters can break everything at once; the config forbids automerging them.
- Automerge is earned, not default. Security fixes automerge because CI proves what a human glance would. Routine bumps automerge only after the machinery has demonstrated, over enough cycles, that green CI genuinely means safe.
- The dashboard is the console. When something seems stuck, the answer is almost always there: waiting on schedule, held by cooldown, or a config error the bot is telling you about.
#Set it up on your own project
The five-minute version, for a project on GitHub:
- Install the Mend Renovate app on your repo (choose Renovate Only, mode Interactive).
- Merge the onboarding PR it opens, or commit a minimal
renovate.jsonyourself:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:best-practices", "schedule:weekly"],
"minimumReleaseAge": "5 days",
"labels": ["dependencies"]
}
- Make sure CI runs on pull requests, so bot PRs get the same verification as yours. If there is no CI yet, that is the first thing to fix; a dependency bot without tests is just automated risk.
- Watch the Dependency Dashboard issue it opens, and read your first few PRs end to end, release notes included.
Never add a dependabot.yml alongside Renovate. GitHub's Dependabot would open duplicate PRs for the same updates. Keep Dependabot's alerts on (they're a data source Renovate reads for security PRs) and leave version updates to one bot.
#Practice
- Open the Dependency Dashboard issue on a repo that has one. For each pending update, say out loud why it hasn't merged yet: schedule, cooldown, or awaiting review.
- Read one Renovate PR top to bottom, including the embedded release notes. Find one changed thing that could matter to the code, even if the answer is "nothing here affects us."
- Tick a dashboard checkbox to force a PR ahead of schedule, watch CI run on it, and merge it yourself.
- In any project, run your package manager's outdated command (
pnpm outdated,npm outdated) and compare what it shows against what the bot has proposed. If they differ, work out which policy (cooldown, grouping, held majors) explains the gap.
#Going deeper
- Renovate's upgrade best practices: the maintainers' own advice on cadence, grouping, and automerge.
- The Dependency Dashboard: everything the checkboxes can do.
- renovate-config: this owner's shared preset and self-hosted runner, documented as a copyable reference, including the token permissions that tripped us and the incidents that drove the self-hosted move.
- Renovate configuration options: the full vocabulary once you outgrow the starter config.