VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author
Reading time
7 min
Rendered
Read aloud
never

PROP-020 — Install hooks

01Status: IMPLEMENTED (specified 2026-06-24 in an owner-requested design session; verified against the tree 2026-07-25 by the spec-actualization campaign). [hooks] parses in vibe-core (manifest/package/hooks.rs), vibe-workspace/src/hooks.rs runs the whole contract — the pre/post phases, the Git-Bash-first Windows interpreter selection, the trust gate over DEFAULT_ALLOWED_GROUPS, the ran / skipped / failed statuses and the VIBE_HOOK_* environment — and vibe-install's apply pipeline drives it. R1 successor 4503fdb6/9c545f0d makes the exact nonempty materialisation report the sole hook-rerun trigger, including verify repair. One of four orthogonal specs carved from the bridge-packages design (the others: PROP-021 submodule sources, PROP-022 materialization modes, PROP-023 bridge packages). The four compose to solve bridge packages but each stands alone — hooks exist for any package, not only bridges.

02Related: PROP-009 (the install/materialise pipeline hooks slot into), PROP-007 (workspace + vibedeps/), PROP-022 (a hook's working tree is the materialised slot; how its edits are reset on update is a materialization-mode property), PROP-015 §2.6 (skill projection reads the slot a hook prepared), PROP-019 §2.13 (the same "a build/script the user installs is code they chose to run" trust posture), PROP-000 §20 (the publish token a hook never sees).

1. Motivation

1.1 The problem — install is pure file I/O, with no preparation step

  • 03Today vibe install resolves, fetches, materialises a package's tree into vibedeps/, regenerates boot artefacts, and writes the lockfile — all pure file copying.
  • A package that needs a preparation step after its content lands (normalise a vendored layout, generate a derived file, assemble a clean skill subtree out of an upstream repo's mess) has nowhere to put it.
  • 04The forcing case is bridge packages (PROP-023): a maintainer wraps someone else's repository whose structure does not match vibevm conventions, and needs to bring it into order before vibevm's skill machinery reads it.
  • But the need is general — any package may want a post-materialise step — so hooks are a universal mechanism, not a bridge-only feature.

1.2 What this is — declared lifecycle scripts, run per package

  • 05A package may declare pre-install / post-install scripts in its manifest. vibevm runs them at fixed points in the install pipeline, in the package's own materialised slot, choosing the right interpreter for the host OS.
  • Only a hook's edits to materialiser-owned recorded payload are ephemeral: reinstall, update or integrity repair restores those bytes per PROP-022, then reruns hooks exactly when that payload diff is nonempty. Hook-created unrecorded state is outside .vibe-slot.toml ownership and survives by design; a rerun may compound it, so hooks must be idempotent until a separate hook-output ownership contract exists.

2. Decisions

2.1 Two phases, anchored to the materialise pipeline

06req r1

07A package declares at most one script per phase:

  • 08pre-install — runs immediately after the package's slot is fully populated (content materialised, submodules fetched per PROP-021) and before vibevm uses the slot (before boot regeneration, before any later vibe skill projection reads it). This is the "bring the tree into order" hook.
  • post-install — runs after the install run is durable for that package (lockfile written, boot artefacts regenerated). For finalisation that needs the package already registered.

09The hook's working directory is the package's materialised slot; it sees exactly the tree vibevm will use.

  • 10On update, reinstall or integrity repair, the materialiser restores its recorded payload, then hooks rerun if and only if that payload diff is nonempty. For copy/hardlink, restoration is the PROP-054 §9.3 record diff: an unrecorded hook-created path survives and may be changed again by the rerun. in-place remains a separate git-native reset whose git clean -dfx removes untracked hook output. The landed cli_hook_rerun::reinstall_runs_post_hook_once_only_for_a_nonempty_force_diff proof records the copy-slot boundary directly: .hook-count is 1 after install, remains 1 after empty reinstall, and becomes 2 after a recorded-payload repair. Copy/hardlink hooks therefore remain responsible for idempotence until a separate hook-output ownership contract exists.
  • Reset remains a materialization-mode property: copy/hardlink restore only the recorded footprint and preserve unrecorded outputs, while in-place reports its git-native change. Hooks consume that exact change report to decide whether the one rerun occurs.

2.2 Interpreter selection is OS-derived

11req r1

12A package ships a phase script as <base>.sh (portable, POSIX shell) and/or <base>.ps1 (PowerShell). The runner picks per host:

  • 13Unix (macOS / Linux): run <base>.sh via bash. A .ps1 is ignored.
  • Windows: prefer <base>.sh via Git Bash when a bash is found (one cross-platform script for .sh packages); else fall back to <base>.ps1 via PowerShell when one is found. A phase that declares a script but finds no usable interpreter is a hard error with a remediation hint — never a silent skip.
  • 14The runner passes a documented environment: VIBE_PACKAGE_GROUP, VIBE_PACKAGE_NAME, VIBE_PACKAGE_VERSION, VIBE_PACKAGE_KIND, VIBE_PACKAGE_DIR (the slot, also CWD), VIBE_HOOK_PHASE. (PROP-024 §2.3 adds VIBE_PROJECT_ROOT, the workspace absolute root, so a build hook can target a gitignored build dir outside the slot; it lands with that work.)
  • The publish token (PROP-000 §20) is never placed in a hook's environment.

15The process runner is an injectable seam (HookRunner) so tests assert the selection logic and argument/env shape without spawning real processes.

2.3 Trust posture — installation is consent, observability is the gate

16req r1

17Historical proposal, superseded. Running a package's hook is running third-party code at install time. This section originally proposed an allow-list and per-run consent; PROP-054 ##INSTALL-IS-CONSENT later replaced that permission model for every extension handler.

  • 18Superseded design: allow-listed groups. A config key (global ~/.vibe/config.toml [hooks].allowed_groups, with a project-level override) lists trusted package groups. org.vibevm is in the allow-list by default. A package whose group is allow-listed runs its hooks with no prompt. This key was never a product input and is not part of the current configuration grammar.
  • Superseded design: first-run consent. On the first hook run of a non-allow-listed package, vibevm prints what will run (phase, script path, group) and asks y/n. Declining skips the hook and marks the package install as hooks-skipped (surfaced, not silent).
  • Superseded design: non-interactive abort. With --assume-yes / in CI, allow-listed packages still run; a non-allow-listed package's hook is not run silently — the install aborts with a hint to either allow-list the group or pass an explicit --allow-hooks opt-in. A script must never execute unseen third-party code by default.

19Current law. Installing the package is the consent to run its declared hooks. There is no [hooks].allowed_groups, first-run prompt, or --allow-hooks permission layer. Safety is the PROP-054 observability model: the manifest is statically inspectable, selected contributions are narrated, and durable run evidence identifies what ran and which package supplied it.

2.4 Hooks are declared in the manifest

20req r1

21Hooks live in a package-role [hooks] table in vibe.toml:

22[hooks]
pre-install  = "hooks/prepare"   # base path, relative to package root
post-install = "hooks/finalise"
  • 23The value is a base path without extension; the runner resolves .sh / .ps1 beside it per §2.2.
  • The table is package-only (its presence on a [project]-role manifest is a validation error, like the other package-only sections).
  • An empty/absent [hooks] means no hooks — the common case.

2.5 Failure semantics are phase-specific

24req r1

25A hook's stdout/stderr stream to the user. A non-zero exit is handled by phase:

  • 26pre-install failure → the package install aborts. The slot is rolled back (removed) and the install reports the failing package; vibevm never registers or projects from a package whose preparation failed.
  • post-install failure → the package is installed but flagged. The package is already durable (lockfile written); the failure surfaces as a warning with the captured output, never a silent success.

3. Rejected alternatives

  • 27Inline command = "..." strings in the manifest instead of files — rejected: a versioned script file is auditable, diffable, and platform-split (.sh/.ps1); an inline string hides the code in TOML and resists review.
  • Running every matching extension on Windows (.sh and .ps1) — rejected: one logical hook per phase keeps behaviour predictable; the selection is a single deterministic choice (§2.2).
  • A general lifecycle (pre-uninstall, pre-build, …) now — deferred: only the two phases the bridge case needs are specified; more can be added later behind their own anchors without disturbing these.

4. Out of scope

  • 28Content scanning / the LLM "antivirus". A future gate that inspects a package's hooks (and code) for malicious behaviour is far-backlog. Until it lands, hook execution is an explicitly accepted risk governed by dependency selection plus PROP-054 observability. This is the deliberate posture, not an oversight — recorded here as the project's stance.
  • Sandboxing / capability-limiting hooks (containers, seccomp). Hooks run with the user's privileges, like cargo build scripts or npm postinstall.
  • Non-git slot reset for in-place — a hook over an in-place (PROP-022 §2.4) package whose source is not git has no cheap reset; in-place therefore requires a git source (PROP-022 §4).

5. Acceptance

  • 29A package with [hooks].pre-install runs the script in its slot before boot regeneration; post-install runs after the lockfile is written.
  • Interpreter selection follows §2.2 on each OS; a declared hook with no usable interpreter errors rather than silently skipping.
  • Any installed package's declared hook is eligible without a second consent prompt or group allow-list; the execution plan and resulting run evidence identify the hook, phase and providing package.
  • A pre-install non-zero exit rolls back the slot and fails the package; a post-install failure installs-but-flags.
  • Updating, reinstalling or integrity-repairing a package restores the prior hook's recorded payload edits (per the materialization mode); a nonempty payload change reruns hooks exactly once and an empty change runs none.
  • The HookRunner seam lets tests assert selection/env/failure paths without spawning real processes.
  • Full self-check.sh green; conform 0/0/0; specmap clean.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/modules/vibe-workspace/PROP-020-install-hooks

.md.xmlllms.txt