Skip to content

Unify all hooks to Python, run via uv #39

@vanducng

Description

@vanducng

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 executionuv 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

  1. 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).
  2. 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.
  3. 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

  • vd-cli supports runtime = "uv" in hooks.toml (manifest parse + HookCommand + tests).
  • All hooks are Python; no .cjs remain.
  • uv run hot-path latency benchmarked and acceptable for PreToolUse hooks (or those kept on python3).
  • Behavior parity verified per ported hook.
  • Docs updated — the vd hooks section + 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 via vd install hooks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions