Skip to content

fix(hooks): block_install_deps misses pipenv; make blocking configurable #58

@misnaej

Description

@misnaej

Problem

claude-hooks/block_install_deps.sh enforces FOUNDATION §2 ("agents must not install dependencies"), but its match is scoped to bare pip/conda at command start:

(^|[;&|]\s*)((python[0-9.]*\s+-m\s+)?pip[0-9.]*|conda) (install|create|env create|update)

This misses pipenv entirely — which is exactly how Option-A (pipenv) consumer repos install/resolve deps (the forge migration recipe explicitly supports pipenv):

  • pipenv install, pipenv sync, pipenv lock, pipenv update — none match (pipenvpip; after pip comes env, not a space/digit). All of these mutate the environment and/or re-resolve dependencies.
  • pipenv run pip install <x> — slips through because pip install isn't at command start (a space precedes it after run). This is the same documented "accepted slip-through" as xargs pip install.

Impact

In a pipenv repo an agent can freely re-resolve and bump unpinned deps — the precise env-breakage failure mode §2 exists to prevent. Observed in practice during a real migration: an agent ran pipenv lock to add one dev tool, which silently re-resolved ~80 unpinned packages to latest (transformers 5.4→5.12, datasets 4→5, torch 2.11→2.12, mypy 1→2…) and broke GPT2LMHeadModel import. The guardrail never fired.

Proposal

Extend block_install_deps.sh to also catch pipenv (and ideally other modern managers):

  • pipenv (install|sync|lock|update|uninstall) — match pipenv as a command word at start / after ;&|.
  • Consider uv pip install, uv add, uv sync, poetry (add|install|update|lock) for completeness.
  • For the wrapper case (pipenv run pip install …, uv run pip install …): catching pip install after a run wrapper would also catch it, but risks re-triggering on quoted bodies (the reason the current anchor is start-only). Safer to match the wrapper forms explicitly ((pipenv|uv|poetry)\s+run\s+pip\s+install) than to broaden the global pip anchor.

Make blocking configurable (default-on)

The block itself should be opt-out-able via [tool.forge] config, not hardcoded. Some repos legitimately want agents to run setup (sandboxed CI, throwaway envs, a trusted local flow), and a flat block forces ! -prefixing every pipenv sync. Proposal:

[tool.forge.hooks]
block_install_deps = true          # default; set false to allow
# or per-manager granularity:
# block_install_deps = ["pip", "conda", "pipenv"]   # omit "pipenv" to allow it

Default stays on (safe baseline, matches FOUNDATION §2). Consumers opt out deliberately. This also gives pipenv repos an escape hatch while the matcher above is tightened.

Notes

  • Read-only commands should stay allowed (pipenv --version, pipenv graph, pipenv run <non-install>), mirroring the existing pip/conda read-only allowlist.
  • Pairs with the FOUNDATION §2 "fail loudly" intent: the block message already tells the user to run it themselves with ! <command> — same UX should apply to pipenv.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingci-testingCI / test infrastructuretier-2-highImportant + high ROI

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions