# Side-Effects Review — Boot-wrapper reinstall: put node on PATH for native postinstalls

**Version / slug:** `fix-bootwrapper-install-path`
**Date:** `2026-06-02`
**Author:** `echo`
**Tier:** 1 (small, well-understood bugfix; ELI16 + side-effects, no converged spec) — but HIGH blast radius (boot shim), reviewed accordingly
**Parent principle:** Cross-Machine Coherence / Bootstrap Robustness — an agent must be able to self-heal its own install

## Summary of the change

The boot wrapper (`instar-boot.cjs` / `instar-boot.sh`, generated by `installBootWrapper`
in `src/commands/setup.ts`) self-heals a missing shadow install by running `npm install`.
That `npm install` was spawned with **no `env` / no PATH augmentation**, so native
postinstall scripts (e.g. `sharp`: `sh -c "node install/check.js || npm run build"`) ran in
a subshell whose PATH — under a launchd-spawned boot child — has no `node`/`npm`, dying with
`command not found`. The reinstall then aborts and the shadow install never heals.

**Observed live (2026-06-02):** a throwaway mini agent could not start — `instar-boot`
looped on "Shadow install missing", and a manual reinstall reproduced it: it failed on
`sharp`'s postinstall (`sh: node: command not found`). Re-running with `node` on PATH
succeeded immediately.

### What changed
- `src/commands/setup.ts` — `installBootWrapper`:
  - **`.cjs` (jsWrapper):** the reinstall `execFileSync(nodeBin, [npmCli, 'install', …])` now
    passes `env: installEnv` where `installEnv` prepends `path.dirname(nodeBin)` to `PATH` and
    sets `npm_config_scripts_prepend_node_path: 'true'`.
  - **`.sh` (bashWrapper):** the reinstall now runs with
    `PATH="$(dirname "$NODE_BIN"):$PATH" npm_config_scripts_prepend_node_path=true` prefixed.
- `src/core/PostUpdateMigrator.ts` — `migrateBootWrapperAbiCheck`: the "already current" guard
  now ALSO requires the `npm_config_scripts_prepend_node_path` marker, so existing agents whose
  wrapper predates this fix are regenerated on update (Migration Parity).

## Decision-point inventory
- reinstall env: augment vs inherit → augment (prepend node dir + npm flag). Inheriting the
  launchd PATH is the bug.
- migration: add a 3rd required marker vs leave 2 → add the marker (otherwise deployed agents
  with the 2 prior markers never regenerate and never get the fix).
- auto-re-port / auto-anything else: none — purely the reinstall env.

## 1. Over-correction risk
The env only AUGMENTS PATH (prepends node dir; keeps the rest) and sets one npm flag. A healthy
agent never enters the reinstall branch at all (`if (!fs.existsSync(SHADOW))`), so the change is
inert unless the shadow install is actually missing. `npm_config_scripts_prepend_node_path` is
npm's own documented option for exactly this case.

## 2. Under-correction risk
The migration regenerates only wrappers missing the new marker; agents already regenerated stay
put (idempotent). The `.sh` variant is for manual/Linux use; the launchd-critical path is `.cjs`.

## 3. Level-of-abstraction fit
The fix lives in the single boot-wrapper generator (`installBootWrapper`) that both `init` and the
migration call — one source, both consumers. No scattering.

## 4. Signal vs Authority
Pure deterministic env construction (no LLM/heuristic). Appropriate.

## 5. External surfaces
None. No route/config-schema change. The generated wrapper gains an env on one spawn.

## 6. Interactions with existing primitives
The reinstall's debounce marker, the ABI-aware node self-heal, and the version-managed-node
candidate discovery are all unchanged; this only adds an env to the npm spawn and a 3rd marker
to the migration's idempotency sniff (composes with the existing two).

## 7. Rollback cost
Trivial: drop the `env`/PATH additions and the 3rd marker clause. No persistent state.

## Migration parity
- New agents: `installBootWrapper` (via `init`) writes the fixed wrapper.
- Existing agents: `migrateBootWrapperAbiCheck` regenerates `instar-boot.cjs` because the new
  `npm_config_scripts_prepend_node_path` marker is absent. Idempotent — re-running skips once the
  marker is present.

## Tests
`tsc --noEmit` clean. `PostUpdateMigrator-bootWrapperAbiCheck` extended to 8 tests (all green):
the "skip" idempotency case now requires all THREE markers; a new regen case for a wrapper with
the two prior markers but missing the install-path marker; a generated-content test asserting the
`.cjs` reinstall passes `env: installEnv` with `npm_config_scripts_prepend_node_path` + the PATH
prepend AND that the generated `.cjs` passes `node --check` (guards against template-escaping
bricking the boot shim); a `.sh` generated-content test. 30 sibling boot-wrapper tests
(`bootWrapperCjs`, `plist-coherence`, `lifeline-shadow-install-self-heal`, `init-join-launchd-handoff`)
all still green — no regressions.
