depcurfew v1.6.1
On this page

Start with Bundler

What the README and the knowledge base say about these tools, on one page.

Five-minute start

  1. npx depcurfew doctor works before init: without depcurfew.json it compares the repository with the policy init would write and shows, as notes in a block of their own, what init and apply would change, for example a package manager that installs new versions without a release-age delay, with the settings apply would write for it. These notes never make it fail.
  2. npx depcurfew init writes depcurfew.json with 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.minimum keeps the longest one, and init says where it found it.
  3. npx depcurfew explain shows what it detected and what the policy means for each tool.
  4. npx depcurfew apply --dry-run prints the changes as a diff, without writing anything.
  5. npx depcurfew apply writes them. Run it again whenever depcurfew.json changes; a second run with the same policy changes nothing.
  6. npx depcurfew doctor reports 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" } that init writes, a warning.
  7. 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.lock or gems.locked in the directory means Bundler; with both gems.rb and Gemfile, Bundler reads gems.rb, and so does depcurfew (only a regular file counts, as for Bundler: a directory named gems.rb does not). The version is the one under BUNDLED WITH at the end of the lockfile, which Bundler installs and switches to. The bundler input of ruby/setup-ruby (a version; Gemfile.lock, default, latest and none name none), gem install bundler -v <version> (also -v<version>, --version=<version> or bundler:<version>) and bundle _<version>_ install in a CI job or a Dockerfile count as versions too. depcurfew explain lists 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 the cooldown: days of the source call that serves them. apply writes the policy's delay in whole days, rounded up ("36h" becomes cooldown: 2), into source "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 or begin (with or without parentheses, with one existing cooldown: or :cooldown =>, and with a comment after it), and keeps the rest of the line and of the file as they are. The cooldown: is read as Ruby reads an integer (010 is 8 days, 0x10 16).
  • 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 =begin and =end, everything after __END__ (or after a NUL, ^D or ^Z outside 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 exactly END-X) are no calls. \r\n is one newline, for apply and doctor alike, 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 %w literal or a modulo, and so with /, <<, ? and : after such a name and a space; also alias, undef and syntax errors (an end or ) without its opening, a do or if without its end). 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 (?€or is a character literal for one and a ternary for the other), =begin followed by ^D or ^Z, and $0 followed by a name. Such a Gemfile is not edited at all (cooldown-not-written with the line and what to change there, such as platforms(%w[ruby])), and doctor does 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 gives cooldown-not-written with its line, and so does a Gemfile without source "https://rubygems.org". So do a source inside a block, method, class, condition, loop, lambda or begin, which Ruby runs only when that code runs, a cooldown: given twice (Ruby keeps the last of two equal keys, and Bundler takes a symbol key over a string key), a cooldown: whose value goes on (cooldown: 7 - 7, or a next line that starts with &&, ||, and or or, 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; doctor reports the same place as an error of config-drift, and as a warning when the call already has a cooldown: at least as long as the policy, since depcurfew cannot tell whether Ruby runs it. The fix never asks for a shorter cooldown:. The Gemfile has no markers, so a longer cooldown: of a plain call written by hand is replaced (cooldown-shortened). Other gem servers keep their own cooldown:; cooldown: 0 exempts 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 once Gemfile.lock exists, which 4.0.14 fixed; older versions accept the keyword and ignore it without a word. apply writes nothing for an older pinned version (version-too-old, which names bundle update --bundler), and doctor reports BUNDLED WITH and the CI versions below what the project needs (manager-version-too-old).
  • What the delay covers. Bundler applies it when it picks versions: bundle install without a lockfile or for gems added to the Gemfile, bundle update, bundle lock --update and bundle add. Versions already in Gemfile.lock install 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/config is not written. Bundler's cooldown setting (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/config is usually local and git-ignored (bundle gem ignores /.bundle/). apply leaves it alone. The rule bundler-config-weakens reports BUNDLE_COOLDOWN with fewer days than the policy, or 0, in .bundle/config (which Bundler reads before the environment), in the env of a CI workflow, written to $GITHUB_ENV by an earlier step of the job, before a bundle command, in a Dockerfile ENV or as the default of a Dockerfile ARG (which RUN gets as a variable, under ENV), bundle config set cooldown N (with --local or --global, and the older bundle config cooldown N) in a run step or a RUN instruction, which writes the setting for the bundle commands after it, and a shorter --cooldown on bundle install, update, lock, add or cache in a CI job or a Dockerfile, also as Bundler's CLI reads them: bundle i, bundle package and bundle pack, an unambiguous prefix such as bundle inst, a bare bundle, which installs, and the hidden bundle install_or_cli_help (also install-or-cli-help), which runs install. .bundle/config is read as Bundler reads it, line by line and not as YAML: quotes stay unless they enclose the whole value (BUNDLE_COOLDOWN: "14" # two weeks is the text "14", so 0), # starts a comment, the last line of a key counts, and only a key spelled exactly BUNDLE_COOLDOWN at the top level is the setting. Bundler reads the setting with to_i, so a value that does not start with a digit (an empty one, ~, true, 0x10) turns the delay off and 1e3 is 1 day; a number in a workflow env is 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, apply says that they do not apply to Bundler (exclude-unsupported). To take a newer version of one gem sooner, review it and run bundle update <gem> --cooldown 0 --conservative.
  • Install scripts are native extensions. Bundler builds the native extensions of a gem (its extconf.rb, Rakefile, CMakeLists.txt or Cargo.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. With installScripts.default: "deny", apply and explain say 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); doctor reads only the version of Gemfile.lock, not its gems and sources, so review such sources by hand.
  • Bots. apply gives Dependabot bundler entries the cooldown (their own exclude stays) and Renovate the delay at the top level, which covers its bundler manager. The Renovate rule for the policy's npm exceptions is limited to the npm datasource, so an npm name does not exempt a gem of the same name. bot-cooldown-mismatch compares the bots' delay for gems with the cooldown: of the Gemfile, as for the other package managers; Renovate rules by manager bundler, category ruby or datasource rubygems count. Renovate updates Gemfile, not gems.rb.
  • Not covered. check, the Claude Code hook and depcurfew mcp do not look gems up on rubygems.org (gem install, bundle add). doctor does not check the gems of Gemfile.lock (lockfile-forbidden-source, lockfile-install-scripts and known-malicious do not apply to it) and does not read ~/.bundle/config, BUNDLE_APP_CONFIG or BUNDLE_GEMFILE. The Bundler a ruby Docker image comes with is not read: Bundler switches to the version of BUNDLED 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

ToolRelease-age delayExceptionsInstall-script allow-list (Python: packages built from source; Composer: plugins)Dependency sourcesVerified
Bundlercooldown in Gemfile, gems.rb (days), since 4.0.13nonenonenone2026-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.