depcurfew v1.6.1
On this page

Start with uv, pip, Poetry, PDM and Pipenv

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"
  }
}

Python projects

depcurfew configures uv, Poetry, PDM, Pipenv and pip from the same depcurfew.json, and the bots for Python packages: Dependabot pip and uv entries and Renovate.

  • Detection. A Python tool counts as used when its lockfile is there, when pyproject.toml has its table, or when its own configuration file or a CI setup step shows it; requirements.txt means pip. depcurfew explain lists what it found.
  • Package names. Python packages on the policy's package lists (the cooldown exceptions, installScripts.allow and installScripts.deny) take the prefix pypi:, for example "pypi:docopt". Names without a prefix stay npm names, so an exception for an npm package never applies to a Python package of the same name. Ruby gems, JSR packages, Rust crates and NuGet packages have no names on these lists, since no setting depcurfew writes takes one: an entry such as "gem:rails" or "cargo:serde" is an error that says to remove it, and an entry with another prefix is an error that lists the prefixes.
  • Install scripts are builds. A wheel installs without a build step, so no build code runs; a source distribution, a git repository or a local directory is built, which runs its build code. Forbidding builds does not keep an installed package's own code from running later, whenever Python imports it. installScripts.default: "deny" forbids building, and the packages in installScripts.allow are the exceptions. None of the three tools can only allow a build, so these packages are always built from source, even where a wheel exists; apply says so, and doctor lists the packages in the lockfile that would be built. Packages in installScripts.deny must not be built: doctor reports one that a tool would still build (Poetry, PDM, pip and Pipenv build git, local and URL dependencies whatever their settings say), and one without wheels that cannot be installed while building is blocked.
  • pip reads no project configuration. apply writes the build options into requirements.txt. The release-age delay cannot live in the project, so apply prints the environment variable to set where pip runs. pip reads the options of the files a requirements file includes with -r and -c, and the options after a requirement on its line, into the same install, so an option there that makes pip build packages from source again (a package name or :all: for building from source, --only-binary=:none:) undoes --only-binary=:all:: apply comments such option lines out in the included files inside the repository (they come back when the policy stops forbidding builds), and warns with the file and line where it cannot (build-switch-undone for an option after a requirement or next to other options, requirements-include-unread for a URL, a path outside the repository or a missing file). doctor reports them (config-drift, and unreadable-config for an include it cannot read), and follows the options in pip's order when it decides whether building is forbidden.
  • Lock again after apply. uv.lock records the release-age delay, and PDM checks pdm.lock against all of its resolution settings, the delay and the build ban included. After apply changes them, uv sync --locked and uv lock --check fail until you run uv lock, and pdm lock --check and pdm install --check until you run pdm lock; apply says so (lockfile-out-of-date), and so does doctor (the rule lockfile-out-of-date) while the lockfile is not locked again: it compares what uv.lock records with the settings as uv does, and the hash in pdm.lock with the one PDM computes from pyproject.toml. poetry.lock and Pipfile.lock do not depend on the settings apply writes.
  • PDM and Pipenv have no setting that pins their own version. Where nothing pins it, apply configures for the latest version depcurfew knows and names that version (version-not-pinned). What CI jobs and Dockerfiles install counts as a version, and doctor checks it (manager-version-too-old): the version input of pdm-project/setup-pdm (head names none); pdm==<version>, pipenv==<version>, uv==<version> and poetry==<version> in pip install, pipx install or uv tool install, with a variable read from the env of the job or the ENV and ARG of the stage (pip install "poetry==${POETRY_VERSION}"); and the uv of the official uv image a Dockerfile copies from or starts from (COPY --from=ghcr.io/astral-sh/uv:0.8.22 /uv /uvx /bin/, FROM ghcr.io/astral-sh/uv:0.8.22-python3.12-bookworm; latest and a base name alone name no version). Pipenv takes the delay in whole days only (rounded up), has no exceptions and no project setting that forbids building; apply says what to set where Pipenv runs instead. doctor reads pdm.lock and Pipfile.lock, and reports settings that turn the delay or the build ban off without a word: Pipenv's pure-Python resolver and PDM's uv mode.
  • Hatch has no settings of its own. Its environments install with uv or pip, so a Hatch project is covered by the uv configuration (add "uv" to targets when its environments use the uv installer) or by the environment variables of pip.
  • Running depcurfew. depcurfew is a Node.js program; a Python project without package.json runs it as In a repository without package.json describes.

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
uvexclude-newer in uv.toml, pyproject.toml (duration string), since 0.9.17exclude-newer-package, since 0.9.25no-binary-package in pyproject.tomlnone2026-10-30 – 2026-11-09
pipuploaded-prior-to (days) on the command line or in the environment, no project file, since 26.1.0none--no-binary in requirements.txtnone2026-10-30 – 2026-11-11
Poetrysolver.min-release-age in poetry.toml (days), since 2.4.0solver.min-release-age-exclude, since 2.4.0installer.no-binary in poetry.tomlnone2026-10-30 – 2026-11-03
PDMexclude-newer in pyproject.toml (duration string), since 2.26.9exclude-newer-override, since 2.29.1no-binary in pyproject.tomlnone2026-10-30 – 2026-11-03
Pipenvcool-down-period in Pipfile (duration string), since 2026.6.2nonenonenone2026-10-30 – 2026-11-11

⚠️ official sources disagree (depcurfew takes the safer reading) · ❓ official sources are incomplete. Every value with its source: docs/knowledge.md.

Protecting the whole machine

ToolUser fileSettings apply --user writes (versions that read them there)Needs the exact version
uvLinux, macOS: $XDG_CONFIG_HOME/uv/uv.toml (when it is an absolute path), else ~/.config/uv/uv.toml
Windows: %APPDATA%\uv\uv.toml, else ~\AppData\Roaming\uv\uv.toml
exclude-newer (0.9.17+), exclude-newer-package (0.9.25+), no-build (0.2.12+), no-build-package (0.2.12+), no-binary-package (0.2.12+)yes
pipLinux: $XDG_CONFIG_HOME/pip/pip.conf, else ~/.config/pip/pip.conf
macOS: ~/Library/Application Support/pip/pip.conf (when the folder exists), else ~/.config/pip/pip.conf
Windows: %APPDATA%\pip\pip.ini, else ~\AppData\Roaming\pip\pip.ini
26.2.0+: Linux: $XDG_CONFIG_HOME/pip/pip.conf, else ~/.config/pip/pip.conf
macOS: $XDG_DATA_HOME/pip/pip.conf (when the folder exists), else ~/Library/Application Support/pip/pip.conf (when the folder exists and $XDG_DATA_HOME is not set), else ~/.config/pip/pip.conf
Windows: %APPDATA%\pip\pip.ini, else ~\AppData\Roaming\pip\pip.ini
26.3.0+: Linux: $XDG_CONFIG_HOME/pip/pip.conf (when it is an absolute path), else ~/.config/pip/pip.conf
macOS: $XDG_DATA_HOME/pip/pip.conf (when it is an absolute path and the folder exists), else ~/Library/Application Support/pip/pip.conf (when the folder exists and $XDG_DATA_HOME is not an absolute path), else ~/.config/pip/pip.conf
Windows: %APPDATA%\pip\pip.ini, else ~\AppData\Roaming\pip\pip.ini
uploaded-prior-to (26.1.0+), only-binary (7.1.0+), no-binary (7.1.0+)yes
Poetry2.0.0+: Linux: $XDG_CONFIG_HOME/pypoetry/config.toml, else ~/.config/pypoetry/config.toml
macOS: ~/Library/Application Support/pypoetry/config.toml
Windows: %APPDATA%\pypoetry\config.toml, else ~\AppData\Roaming\pypoetry\config.toml
macOS with platformdirs before 4.6.0: ~/Library/Application Support/pypoetry/config.toml
macOS with platformdirs 4.6.0–4.9.6: $XDG_CONFIG_HOME/pypoetry/config.toml, else $XDG_DATA_HOME/pypoetry/config.toml, else ~/Library/Application Support/pypoetry/config.toml
macOS with platformdirs 4.9.6–4.11.8: $XDG_CONFIG_HOME/pypoetry/config.toml, else ~/Library/Application Support/pypoetry/config.toml
macOS with platformdirs 4.11.8+: $XDG_CONFIG_HOME/pypoetry/config.toml (when it is an absolute path), else ~/Library/Application Support/pypoetry/config.toml
solver.min-release-age (2.4.0+), solver.min-release-age-exclude (2.4.0+), installer.only-binary (2.0.0+), installer.no-binary (1.2.0+)no
PDMLinux: $XDG_CONFIG_HOME/pdm/config.toml, else ~/.config/pdm/config.toml
macOS: ~/Library/Application Support/pdm/config.toml
Windows: ~\AppData\Local\pdm\pdm\config.toml
macOS with platformdirs before 4.6.0: ~/Library/Application Support/pdm/config.toml
macOS with platformdirs 4.6.0–4.9.6: $XDG_CONFIG_HOME/pdm/config.toml, else $XDG_DATA_HOME/pdm/config.toml, else ~/Library/Application Support/pdm/config.toml
macOS with platformdirs 4.9.6–4.11.8: $XDG_CONFIG_HOME/pdm/config.toml, else ~/Library/Application Support/pdm/config.toml
macOS with platformdirs 4.11.8+: $XDG_CONFIG_HOME/pdm/config.toml (when it is an absolute path), else ~/Library/Application Support/pdm/config.toml
strategy.exclude-newer (2.27.0+)yes
Pipenvnonenoneno

More: Protecting the whole machine.

doctor rules for these tools

python-config-ignored

error · A Python tool ignores a setting where it is written.

uv reads uv.toml instead of [tool.uv] when both exist, and in a workspace only the root; pip reads no configuration from the project (a pip.conf counts only where PIP_CONFIG_FILE names it) and rejects options it does not know in requirements files, so a CI job or a Dockerfile stage of the python image installs without the release-age delay unless it sets PIP_UPLOADED_PRIOR_TO or passes --uploaded-prior-to; PDM in uv mode installs without only-binary; Pipenv with resolver_backend (or its older name resolver) set to "pure-python" or "uv" locks without cool-down-period. A setting in such a place looks effective but is not, and an exclude-newer date freezes dependencies instead of delaying new versions.

Fix: Move the setting to the file the tool reads.

lockfile-out-of-date

warning · A uv.lock or pdm.lock was locked with other settings than the project has now.

uv records the release-age delay (exclude-newer and exclude-newer-package) in uv.lock, and PDM checks pdm.lock against a hash of the dependencies, the sources and the [tool.pdm.resolution] table, the delay and the build ban included. After a change, by depcurfew apply or by hand, uv sync --locked, uv lock --check, pdm lock --check and pdm install --check fail until the lockfile is locked again, and the lockfile still holds what was resolved under the old settings.

Fix: Lock again (uv lock or pdm lock) and commit the lockfile.

Every rule: doctor rules.

Sources

Every fact behind these tables, with its official source, is in the knowledge base: uv, pip, Poetry, PDM, Pipenv.