Repository navigation
Install trail without sudo - #734
Conversation
…and EEXIST; check Node first
🌱 graft blast radius3 areas changed → 4 areas can be affected. 16 dependent symbols, depth 2. flowchart TB
A0(("Host Management<br/>10 symbols"))
A1(("CLI Upkeep<br/>3 symbols"))
A2(("Claude Initialization<br/>2 symbols"))
A3(("Install Paths<br/>1 symbol"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1,A2,A3 reached;
Who knows this code — 3 people across 7 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 16 dependent symbols, grouped by areaHost Management — 10 symbols in 10 files
CLI Upkeep — 3 symbols in 3 files
Claude Initialization — 2 symbols in 2 files
Install Paths — 1 symbol in 1 file
Test signal per changed area — 1 ✓ · 2 –Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
33 test suites also reference this code33 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.
Open the interactive graph → — click an area to see the code that changed, and the line that reaches it. |
Issue: At least four testers couldn't install or upgrade trail: npm asked for sudo, reinstalls failed with ENOTEMPTY or EEXIST, and an old Node broke setup without saying so.
Why: On a Node from nodejs.org only root can write npm's global folder; a stopped npm install leaves a folder every later install trips on; and trail said it needed Node 20 when it really needs 22.3 (or 20.16).
Fix:
trail upgradenow clears npm's leftovers and tries again, and turns EACCES into a no-sudo fix;trailstops on an old Node with one plain line; the README, the trail skill and the session hook show the no-sudo setup.Moves: installs that reach setup,
installvsinit_completedper install id over the last 7 days (PostHog, Metabase db 11,properties->>'cli_name' = 'trail'): 478 of 1,516 install ids (31.5%) on 2026-10-29.What changed
trail upgrade/graft upgrade(runUpgrade,src/cli-meta.ts): npm's stderr is kept so itsnpm error code/path/destlines can be read..<name>-<hash of its path>before writing the new one. The name is the same every run, so one stopped install breaks every later one. Trail removes that folder (only when it's npm's dot-named sibling of the package) and tries again.trail/graftlink in npm's bin folder from another copy of trail. Removed and retried only when it points into a trail or graft install, or at nothing. Any other file is left alone.~/.npm-global), with the PATH line for the user's shell (zsh, bash, fish). No sudo.src/node-version.ts, both bins):trailandgraftcheck Node before anything else loads (everything after is a dynamic import).enginesis now^20.16.0 || >=22.3.0, whereprocess.getBuiltinModuleexists; on Node 18,trail init"worked" but couldn't read past sessions.src/claude/skill-template.ts,src/claude/shim-template.ts): where they tell a teammate's Claude to runnpm install -g @trailhq/trail-code, they now add: on EACCES, never use sudo (Claude can't type the password anyway); offer to point npm at~/.npm-globaland add it to PATH, then install again. One short clause in the skill description, which every teammate's Claude sees; the steps in the skill body and the hook. Wired repos pick up the new text at their next session.Choices I made
process.getBuiltinModule(22.3, backported to 20.16). This week 50 install ids ran Node 20; a flat>=22would have cut off the ones on 20.16+, for whom trail works. 8 ran Node 18, 1 Node 16.npm i -geither). The bin is the check that reaches people; npm's own EBADENGINE warning now names the right range.npm i -gthat fails with EACCES fails before any trail code runs, so the fix for first installs is the README tip.trail upgradeprints the same fix.Tests
test/node-version.test.ts;test/cli-meta.test.tscovers parsing npm's error, both leftovers (and what's left alone), the no-sudo fix per shell, and the report.npm test: the full suite ran while the reproductions were installing packages, and 36 hook and journey tests failed on timing. Re-run alone, those 22 files give the same result onorigin/mainand on this branch: 159 pass, 5 fail, the same 5. They fail onorigin/mainon this machine too, because they depend on what's installed here, such as atrailon PATH; CI runs them on a clean machine.trail init(the skill and hook it writes carry the no-sudo line), Node 18 stoppingtrailandgraftwith nothing written, a plaintrail upgrade, EACCES, ENOTEMPTY, a stale trail link, someone else'strailfile left alone, two leftovers in one run, and an unreachable registry showing npm's own error. Not covered: a real sudo install, Windows, and a live Claude session following the new hook line.test/skills-prompt.test.ts, D17) that this PR doesn't touch. It passes locally, and main's own Windows runs today fail in the same family of hook tests; the job is being re-run.trail check: one flagged conflict, a false match.src/claude/skill-template.ts"brings back home trail never" from the Oct 27 learning about tests run with the real HOME (the shim stood down for a copy Claude never loads). The new line only shares those words: it tells Claude to set npm's prefix to a folder in the person's home, and has nothing to do with HOME in tests.Follow-ups (not in this PR)
sudo npm i -g, postinstall writes~/.graft/telemetry.jsonas root (macOS keeps HOME under sudo). The user's own trail can't read it and makes a new install id each run, which splitsinstallfrominit_completedin this metric. Postinstall could hand the folder back toSUDO_UID.trail init" postinstall line is never shown on a global install (npm 7+ hides it).🤖 Generated with Claude Code