On this page
Start with Bundler
What the README and the knowledge base say about these tools, on one page.
Five-minute start
npx depcurfew doctorworks beforeinit: withoutdepcurfew.jsonit compares the repository with the policyinitwould write and shows, as notes in a block of their own, whatinitandapplywould change, for example a package manager that installs new versions without a release-age delay, with the settingsapplywould write for it. These notes never make it fail.npx depcurfew initwritesdepcurfew.jsonwith the default policy below and lists the tools it detected. When the repository already sets a longer release-age delay for one of them,cooldown.minimumkeeps the longest one, andinitsays where it found it.npx depcurfew explainshows what it detected and what the policy means for each tool.npx depcurfew apply --dry-runprints the changes as a diff, without writing anything.npx depcurfew applywrites them. Run it again wheneverdepcurfew.jsonchanges; a second run with the same policy changes nothing.npx depcurfew doctorreports configuration that silently does not work and drift from the policy, and exits with 1 when it finds an error or, with the"doctor": { "failOn": "warning" }thatinitwrites, a warning.- Add the GitHub Action so that doctor runs on every push and reports to code scanning.
The default policy:
{
"$schema": "https://unpkg.com/depcurfew/schema.json",
"version": 1,
"cooldown": {
"minimum": "3d",
"exclude": [],
"securityUpdates": "bypass"
},
"installScripts": {
"default": "deny",
"allow": [],
"deny": []
},
"sources": {
"git": "deny",
"remoteTarball": "deny",
"file": "allow",
"directory": "allow"
},
"targets": "auto",
"doctor": {
"failOn": "warning"
}
}
Ruby projects
depcurfew configures Bundler (Ruby gems from rubygems.org) from the same depcurfew.json, and the bots for gems: Dependabot bundler entries and Renovate.
- Detection. A
Gemfile,gems.rb,Gemfile.lockorgems.lockedin the directory means Bundler; with bothgems.rbandGemfile, Bundler readsgems.rb, and so does depcurfew (only a regular file counts, as for Bundler: a directory namedgems.rbdoes not). The version is the one underBUNDLED WITHat the end of the lockfile, which Bundler installs and switches to. Thebundlerinput ofruby/setup-ruby(a version;Gemfile.lock,default,latestandnonename none),gem install bundler -v <version>(also-v<version>,--version=<version>orbundler:<version>) andbundle _<version>_ installin a CI job or a Dockerfile count as versions too.depcurfew explainlists what it found. - The release-age delay is the
cooldown:of the source. Bundler 4.0.13 and later hold back gem versions younger than thecooldown:days of thesourcecall that serves them.applywrites the policy's delay in whole days, rounded up ("36h"becomescooldown: 2), intosource "https://rubygems.org"of the Gemfile, which is committed, so every developer and CI run get it. It edits only a plain call with a literal URL on a line of its own that starts a statement at the top level of the Gemfile, outside any block, method, class, condition, loop, lambda orbegin(with or without parentheses, with one existingcooldown:or:cooldown =>, and with a comment after it), and keeps the rest of the line and of the file as they are. Thecooldown:is read as Ruby reads an integer (010is 8 days,0x1016). - The Gemfile is read as Ruby reads it. Bundler runs the Gemfile as Ruby code, so depcurfew reads it as Ruby's lexer does: comments, lines between
=beginand=end, everything after__END__(or after a NUL,^Dor^Zoutside strings and comments), strings and%q,%w,%(...)and other literals over several lines with any delimiter, and the bodies of heredocs (also<<~"END-X", which ends only at a line that is exactlyEND-X) are no calls.\r\nis one newline, forapplyanddoctoralike, as for Ruby. Where Ruby's reading depends on something only Ruby knows, depcurfew stops: after a name that may be a local variable or a method,platforms %w[ruby]is a%wliteral or a modulo, and so with/,<<,?and:after such a name and a space; alsoalias,undefand syntax errors (anendor)without its opening, adoorifwithout itsend). It stops as well where Ruby 3.4 and newer (Prism, the default parser since 3.4) and Ruby 3.3 and older (parse.y) read the same text differently:?before a character beyond ASCII (?€oris a character literal for one and a ternary for the other),=beginfollowed by^Dor^Z, and$0followed by a name. Such a Gemfile is not edited at all (cooldown-not-writtenwith the line and what to change there, such asplatforms(%w[ruby])), anddoctordoes not count a cooldown in it. - What apply does not edit is reported. A source with a block, a modifier (
if,unless), other options, a URL depcurfew cannot read or a call over several lines givescooldown-not-writtenwith its line, and so does a Gemfile withoutsource "https://rubygems.org". So do a source inside a block, method, class, condition, loop, lambda orbegin, which Ruby runs only when that code runs, acooldown:given twice (Ruby keeps the last of two equal keys, and Bundler takes a symbol key over a string key), acooldown:whose value goes on (cooldown: 7 - 7, or a next line that starts with&&,||,andoror, which Ruby 4.0 joins to the call as it joins.method), and a source whose URL depcurfew cannot read also next to a plain call;doctorreports the same place as an error ofconfig-drift, and as a warning when the call already has acooldown:at least as long as the policy, since depcurfew cannot tell whether Ruby runs it. The fix never asks for a shortercooldown:. The Gemfile has no markers, so a longercooldown:of a plain call written by hand is replaced (cooldown-shortened). Other gem servers keep their owncooldown:;cooldown: 0exempts a private one, as Bundler documents. - Bundler 4.0.14 for projects with a lockfile. Bundler 4.0.13 loses the
cooldown:of a Gemfile source onceGemfile.lockexists, which 4.0.14 fixed; older versions accept the keyword and ignore it without a word.applywrites nothing for an older pinned version (version-too-old, which namesbundle update --bundler), anddoctorreportsBUNDLED WITHand the CI versions below what the project needs (manager-version-too-old). - What the delay covers. Bundler applies it when it picks versions:
bundle installwithout a lockfile or for gems added to the Gemfile,bundle update,bundle lock --updateandbundle add. Versions already inGemfile.lockinstall as they are, also younger ones, so review lockfile changes as before. A version without a publication date (a gem server without the v2 compact index) is not held back. When every version that fits is too new, Bundler stops and names--cooldown 0. .bundle/configis not written. Bundler'scooldownsetting (bundle config set cooldown N,BUNDLE_COOLDOWN) applies to every source and takes the place of the Gemfile's, so it would also end the exemption of a private server, and.bundle/configis usually local and git-ignored (bundle gemignores/.bundle/).applyleaves it alone. The rulebundler-config-weakensreportsBUNDLE_COOLDOWNwith fewer days than the policy, or0, in.bundle/config(which Bundler reads before the environment), in theenvof a CI workflow, written to$GITHUB_ENVby an earlier step of the job, before abundlecommand, in a DockerfileENVor as the default of a DockerfileARG(whichRUNgets as a variable, underENV),bundle config set cooldown N(with--localor--global, and the olderbundle config cooldown N) in arunstep or aRUNinstruction, which writes the setting for thebundlecommands after it, and a shorter--cooldownonbundle install,update,lock,addorcachein a CI job or a Dockerfile, also as Bundler's CLI reads them:bundle i,bundle packageandbundle pack, an unambiguous prefix such asbundle inst, a barebundle, which installs, and the hiddenbundle install_or_cli_help(alsoinstall-or-cli-help), which runs install..bundle/configis read as Bundler reads it, line by line and not as YAML: quotes stay unless they enclose the whole value (BUNDLE_COOLDOWN: "14" # two weeksis the text"14", so 0),#starts a comment, the last line of a key counts, and only a key spelled exactlyBUNDLE_COOLDOWNat the top level is the setting. Bundler reads the setting withto_i, so a value that does not start with a digit (an empty one,~,true,0x10) turns the delay off and1e3is 1 day; a number in a workflowenvis read as written.- No exceptions for gems. Bundler has no per-gem exception, and the policy's package lists have no prefix for Ruby gems: the cooldown exceptions of the policy stay npm, Python and Composer names. When the policy has exceptions,
applysays that they do not apply to Bundler (exclude-unsupported). To take a newer version of one gem sooner, review it and runbundle update <gem> --cooldown 0 --conservative. - Install scripts are native extensions. Bundler builds the native extensions of a gem (its
extconf.rb,Rakefile,CMakeLists.txtorCargo.toml) when it installs it, which runs code of the gem, and has no setting that stops this or allows it for some gems only. WithinstallScripts.default: "deny",applyandexplainsay that it cannot be enforced for Bundler (scripts-not-enforceable). - No switch for dependency sources. A Gemfile can name git sources (
git:,github:) and paths (path:), and Bundler has no setting that forbids them (source-not-enforceable);doctorreads only the version ofGemfile.lock, not its gems and sources, so review such sources by hand. - Bots.
applygives Dependabotbundlerentries the cooldown (their ownexcludestays) and Renovate the delay at the top level, which covers itsbundlermanager. The Renovate rule for the policy's npm exceptions is limited to thenpmdatasource, so an npm name does not exempt a gem of the same name.bot-cooldown-mismatchcompares the bots' delay for gems with thecooldown:of the Gemfile, as for the other package managers; Renovate rules by managerbundler, categoryrubyor datasourcerubygemscount. Renovate updatesGemfile, notgems.rb. - Not covered.
check, the Claude Code hook anddepcurfew mcpdo not look gems up on rubygems.org (gem install,bundle add).doctordoes not check the gems ofGemfile.lock(lockfile-forbidden-source,lockfile-install-scriptsandknown-maliciousdo not apply to it) and does not read~/.bundle/config,BUNDLE_APP_CONFIGorBUNDLE_GEMFILE. The Bundler arubyDocker image comes with is not read: Bundler switches to the version ofBUNDLED WITH.
In a repository without package.json
depcurfew is a Node.js program, and a repository without package.json in its root, such as a Rust, .NET, Python, PHP or Ruby project, does not need one to run it.
In GitHub Actions, the GitHub Action needs nothing installed.
Once, by hand, run an exact release with npx:
npx --yes depcurfew@1.5.2 doctor
Name a release that is at least as old as cooldown.minimum of the policy: depcurfew's own Claude Code hook denies a younger one, and so does npm where its min-release-age is set (npx then fails with ETARGET). The example names the newest release that was at least 7 days old when this README was written, usually the one before the newest. Without --yes, npx asks before it downloads a package the project does not have, and without a terminal or in CI it downloads without asking.
Pinned in the repository, keep depcurfew in the tools directory .claude/depcurfew: its own package.json pins depcurfew exactly, its package-lock.json is committed and .claude/depcurfew/node_modules is git-ignored. npm ci installs what that package-lock.json locks, and --prefix runs it in the tools directory, so both commands run from the root of the repository, without cd:
npm ci --ignore-scripts --prefix .claude/depcurfew
node .claude/depcurfew/node_modules/depcurfew/dist/cli.js doctor
doctor reads this copy too: depcurfew-outdated says when the depcurfew that runs is older than it, and the Claude Code hook can run it. To be offered new releases of the pin, you can add a Dependabot npm entry for the directory /.claude/depcurfew; apply gives it the cooldown of the policy like any other npm entry. The tools directory and the Action pin their versions separately, so update both.
Supported tools
| Tool | Release-age delay | Exceptions | Install-script allow-list (Python: packages built from source; Composer: plugins) | Dependency sources | Verified |
|---|---|---|---|---|---|
| Bundler | cooldown in Gemfile, gems.rb (days), since 4.0.13 | none | none | none | 2026-11-03 |
⚠️ official sources disagree (depcurfew takes the safer reading) · ❓ official sources are incomplete. Every value with its source: docs/knowledge.md.
doctor rules for these tools
bundler-config-weakens
error · needs depcurfew.json (without one: a note against the default policy) · .bundle/config, the env of a CI workflow or a bundle command in a CI job or a Dockerfile turns the cooldown of Bundler off or makes it shorter than the policy.
Bundler 4.0.13 and later hold back gem versions younger than the cooldown: of the Gemfile source, but the cooldown setting and the --cooldown option take its place for every source. BUNDLE_COOLDOWN with fewer days than depcurfew.json, or 0, in .bundle/config (read line by line as Bundler reads it, not as YAML: quotes stay unless they enclose the whole value, and # starts a comment), in a CI workflow (env, $GITHUB_ENV, before a bundle command) or a Dockerfile (ENV, ARG), bundle config set cooldown N in a run step or a RUN instruction, and a shorter --cooldown on bundle install, update, lock, add or cache (also bundle i, bundle package and bundle _VERSION_ install) turn the delay off or shorten it while the Gemfile still looks configured. Bundler reads the value with to_i: one that does not start with a digit counts as 0, and 1e3 as 1. A value with a ${{ }} expression gives a warning.
Fix: Remove the setting, the variable or the option, or give it at least the days of the policy; take a newer version of one gem by hand with bundle update GEM --cooldown 0 after reviewing it.
Every rule: doctor rules.
Sources
Every fact behind these tables, with its official source, is in the knowledge base: Bundler.