Generated session hook prepends /usr/local/bin, shadowing a newer Node and misreporting the failure as a format incompatibility
- Dominant language
- TypeScript
- Stars
- 79
- Forks
- 9
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 42
Description
## Summary
In an environment with two Node installations, the generated `tbd-session.sh` SessionStart hook can select the wrong Node runtime, fail, and then report a misleading cause. Three issues compound:
1. The hook unconditionally **prepends** `/usr/local/bin` to `$PATH`. When a newer Node lives at a non-standard prefix that is already earlier on the session `PATH`, this reorders the older Node ahead of it. Since `tbd`'s bin uses a `#!/usr/bin/env node` shebang, `tbd` then runs under the older Node and trips the `engines: >=22.12.0` gate.
2. `tbd_local_can_read_repository()` cannot distinguish "Node too old" from "incompatible repo format" — both surface as exit 1 with stderr suppressed — so the hook prints a **factually wrong** diagnosis.
3. The `npx` fallback runs under the *same* wrong Node, so it can never recover from a Node-version failure. The fallback is structurally doomed in exactly the case that triggers it here.
Net effect: the hook exits 1 and tbd is never primed, with an error message pointing at the wrong thing.
## Environment
- tbd (`get-tbd`) 0.7.1, repo config format f08
- Container with two Node installs:
- `/opt/node22/bin/node` = v22.22.2 (the intended runtime, already on the session `PATH`)
- `/usr/local/bin/node` -> `/opt/node20/bin/node` = v20.20.2
- Session `PATH` before the hook runs has `/opt/node22/bin` **before** `/usr/local/bin` — i.e. correctly ordered
- `tbd setup --auto` itself succeeds, because it runs under the correct Node
## Steps to reproduce
1. Install tbd globally under a Node >=22.12.0 at a non-standard prefix (e.g. via `/opt/node22/bin/npm`).
2. Make `/usr/local/bin/node` resolve to a Node < 22.12.0.
3. Ensure the session `PATH` lists the Node 22 bin dir *before* `/usr/local/bin`.
4. Run `tbd setup --auto` in a repo (succeeds).
5. Trigger the SessionStart hook (`bash .claude/scripts/tbd-session.sh`).
## Expected
The hook uses the Node >=22.12.0 already present on `PATH` and runs `tbd prime` successfully. If no adequate Node exists, the error should say the Node version is the problem.
## Actual
```
[tbd] Local tbd cannot read this repository format; using configured fallback get-tbd@0.7.1.
npm warn EBADENGINE Unsupported engine { package: 'get-tbd@0.7.1', required: { node: '>=22.12.0' }, current: { node: 'v20.20.2', npm: '10.8.2' } }
tbd requires Node.js 22.12.0 or newer; current runtime is 20.20.2. Upgrade Node.js before running tbd.
```
The hook exits 1. The repository format is fine (f08, matching tbd 0.7.1) — the message is wrong.
Direct confirmation that the `PATH` line is the trigger:
| Condition | `which node` | `node --version` | `tbd config get tbd_format` |
| --- | --- | --- | --- |
| Session `PATH`, before the hook's export | `/opt/node22/bin/node` | v22.22.2 | prints `f08`, exit 0 |
| After the hook's `export PATH=...` | `/usr/local/bin/node` | v20.20.2 | exit 1 |
## Root cause
Line references verified against `dd32449` (`packages/tbd/package.json` version 0.7.1).
**1. PATH prepend** — `packages/tbd/src/cli/commands/setup.ts:304`:
```bash
export PATH="$HOME/.local/bin:$HOME/bin:/usr/local/bin:$PATH"
```
The intent (per the adjacent comment, "Prefer common local bin locations") is to make user-local bin dirs reachable in a bare hook environment. But `/usr/local/bin` is almost always already on `PATH`; moving it to the front can only reorder system dirs, and here it demotes the Node the session deliberately put first.
**2. Indistinguishable failure modes** — `setup.ts:281-282`:
```bash
tbd_local_can_read_repository() {
command -v tbd &> /dev/null && tbd config get tbd_format >/dev/null 2>&1
}
```
`command -v tbd` succeeds (the binary exists). `tbd config get tbd_format` then exits 1 — but from the Node gate in `packages/tbd/src/cli/bin-bootstrap.cjs:36-42`, which fires before any tbd logic runs. Because stderr is discarded, the caller cannot tell why, and the message at `setup.ts:336` asserts a format incompatibility that did not occur.
**3. Doomed fallback** — `setup.ts:340`:
```bash
npx --yes "get-tbd@$configured_fallback_version" prime "$@"
```
`npx` resolves through the same `PATH`, so it runs on the same too-old Node, and the downloaded package declares the same `engines` constraint. When the trigger was a Node version mismatch rather than a format mismatch, this path cannot succeed — it just adds a package download and an `EBADENGINE` warning before failing identically.
## Suggested fixes
**Append instead of prepend.** Keeps user-local dirs reachable without reordering system dirs that are already present:
```bash
export PATH="$HOME/.local/bin:$HOME/bin:$PATH:/usr/local/bin"
```
Trade-off: if `/usr/local/bin` were *not* already on `PATH` and held the only usable Node, it would now be lower priority. That seems much rarer than the failure above, and the existing fallback chain still covers it.
**Probe the Node version before invoking tbd**, so the two failure modes separate and the message is accurate:
```bash
tbd_local_can_read_repository() {
command -v tbd &> /dev/null || return 1
local major
major=$(node --version 2>/dev/null) || return 1
major=${major#v}; major=${major%%.*}
[ "${major:-0}" -ge 22 ] 2>/dev/null || return 1
tbd config get tbd_format >/dev/null 2>&1
}
```
**Short-circuit the npx fallback** when the local failure was a Node-version failure, and say so, rather than attempting a download that cannot work.
A lighter-touch variant of the second fix: capture stderr from `tbd config get tbd_format` and branch on whether it matches `requires Node.js`, returning a distinct status the caller can act on.
## Also affected
The same unconditional prepend appears in two other generated scripts:
- `packages/tbd/src/cli/commands/setup.ts:426` (close-protocol script; its own npx fallback is at `:431`)
- `packages/tbd/docs/install/ensure-gh-cli.sh:27`
## Note
Possibly related to the "version-aware generated hooks" work in 0.6.5 — the format-detection path is what makes an exit-1 from `tbd config get tbd_format` read as a format problem specifically.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the generated hook logic in packages/tbd/src/cli/commands/setup.ts at lines 281-340 and 304, then inspect the close-protocol path around 426-431 and docs/install/ensure-gh-cli.sh:27. Reproduce with bash .claude/scripts/tbd-session.sh and compare Node resolution before and after setup. Done means the hook preserves the intended Node selection, distinguishes Node failures from format failures, and does not use a doomed fallback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nodejs, shell, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100