Motivation
Hooks are currently split across two runtimes:
- Node (
.cjs) — session-init, subagent-init, team-context-inject, dev-rules-reminder, scout-block, statusline, task-completed-handler, teammate-idle-handler, lib/*
- Python (
.py) — agent-notify, pr-merge-guard
Two runtimes means two toolchains to install and keep working. Unifying on Python, run via uv gives:
- one language for every hook (shared helpers, one mental model, easier to maintain);
- portable, reproducible execution —
uv run resolves the interpreter + dependencies on demand (PEP 723 inline script deps), no global pip, no system-Python drift;
- fast cold starts (uv is a single static binary, far quicker than
node + an npm install).
Proposal
- Port the Node hooks to Python, preserving behavior. The existing
internal/hooks/*.mjs parity tests are the behavioral contract — replace them with Python equivalents (or keep behavioral tests that assert the same I/O).
- Run hooks via uv — pick one model:
- PEP 723 inline deps (
# /// script block per file) + uv run --script <file> — each hook self-declares its deps; zero shared state.
- uv project (
pyproject.toml + a locked venv in the hooks dir) + uv run <file> — shared deps, one lockfile.
- vd-cli: add a
uv runtime to the hook manifest. claudeconfig.HookCommand emits uv run "$HOME/.claude/hooks/<file>" <args> (or uv run --script …) when runtime = "uv".
Considerations / open questions
- ⚠️ Hot-path latency.
scout-block and pr-merge-guard fire on every PreToolUse. uv run must be near-instant with a warm cache. Benchmark cold + warm; consider uv run --no-sync or a pre-synced venv so the hot path doesn't re-resolve deps each call. This is the main risk — if it adds perceptible latency per tool call, uv on the hot path is a non-starter and those hooks should stay plain python3 (stdlib).
- uv as a dependency. Trades the Node requirement for a uv requirement. uv is a single static binary, trivial to install — acceptable, but document it as a prereq for
vd install hooks.
- uv vs plain
python3. Stdlib-only hooks (the current Python ones) don't need uv at all; runtime = "python3" already works. uv earns its keep only when a hook has third-party deps. Decide: is uv the default, or opt-in per hook (mixed python3 + uv in the manifest)?
- Shebang vs explicit runtime. Prefer the manifest
runtime = "uv" over a #!/usr/bin/env -S uv run shebang (the latter depends on a recent env that supports -S).
- Cross-repo. Hook scripts live in the user's hooks repo (e.g.
~/skills/hooks/); the uv runtime support lives in vd-cli. Sequence: ship vd-cli uv runtime first, then port scripts.
Acceptance criteria
Filed from the hooks-from-local-manifest work (v3.0.0/3.0.1). Related: the hooks now live in a user repo (e.g. ~/skills/hooks/) installed via vd install hooks.
Motivation
Hooks are currently split across two runtimes:
.cjs) —session-init,subagent-init,team-context-inject,dev-rules-reminder,scout-block,statusline,task-completed-handler,teammate-idle-handler,lib/*.py) —agent-notify,pr-merge-guardTwo runtimes means two toolchains to install and keep working. Unifying on Python, run via uv gives:
uv runresolves the interpreter + dependencies on demand (PEP 723 inline script deps), no globalpip, no system-Python drift;node+ annpm install).Proposal
internal/hooks/*.mjsparity tests are the behavioral contract — replace them with Python equivalents (or keep behavioral tests that assert the same I/O).# /// scriptblock per file) +uv run --script <file>— each hook self-declares its deps; zero shared state.pyproject.toml+ a locked venv in the hooks dir) +uv run <file>— shared deps, one lockfile.uvruntime to the hook manifest.claudeconfig.HookCommandemitsuv run "$HOME/.claude/hooks/<file>" <args>(oruv run --script …) whenruntime = "uv".Considerations / open questions
scout-blockandpr-merge-guardfire on everyPreToolUse.uv runmust be near-instant with a warm cache. Benchmark cold + warm; consideruv run --no-syncor a pre-synced venv so the hot path doesn't re-resolve deps each call. This is the main risk — if it adds perceptible latency per tool call, uv on the hot path is a non-starter and those hooks should stay plainpython3(stdlib).vd install hooks.python3. Stdlib-only hooks (the current Python ones) don't need uv at all;runtime = "python3"already works. uv earns its keep only when a hook has third-party deps. Decide: isuvthe default, or opt-in per hook (mixedpython3+uvin the manifest)?runtime = "uv"over a#!/usr/bin/env -S uv runshebang (the latter depends on a recentenvthat supports-S).~/skills/hooks/); theuvruntime support lives in vd-cli. Sequence: ship vd-cliuvruntime first, then port scripts.Acceptance criteria
runtime = "uv"inhooks.toml(manifest parse +HookCommand+ tests)..cjsremain.uv runhot-path latency benchmarked and acceptable forPreToolUsehooks (or those kept onpython3).vd hookssection + a uv prerequisite note.Filed from the hooks-from-local-manifest work (v3.0.0/3.0.1). Related: the hooks now live in a user repo (e.g.
~/skills/hooks/) installed viavd install hooks.