Repository navigation
node-pty native build silently skipped by npm allow-scripts policy, crash-loops the Linux background service #7475
Description
What happened
Two symptoms that turned out to be the same root cause:
- The background service (
t3code.service, systemd) was supposed to run independently of the desktop app, but broke as soon as the desktop app closed — it ended up depending on the desktop app staying open to work at all. - Every coding-agent provider (Claude Code, Codex, Cursor, Grok) showed as unavailable on every single launch.
Diagnosis
Both symptoms trace back to node-pty never getting a working native binary in the pinned runtime at runtime/versions/0.0.33/node_modules/node-pty/. There was no prebuilds/linux-x64/ directory (expected — node-pty@1.1.0 only ships darwin-{arm64,x64} and win32-{arm64,x64} prebuilds, same gap as #2621 and #6012), and no local build/Release/pty.node either, meaning the node-gyp rebuild fallback that's supposed to compile it locally on Linux never ran.
The reason the fallback never ran: the user's ~/.npmrc had allow-scripts=<some-other-package>, npm's script-allowlist config (npm 12, RFC npm/rfcs#868). Under that policy npm silently skips lifecycle scripts for any package not on the allowlist — node-pty's install script (node scripts/prebuild.js || node-gyp rebuild) and msgpackr-extract's were both silently skipped during ensurePinnedRuntimeInstalled's npm install --prefix <staging> t3@<version> (apps/server/src/cloud/pinnedRuntime.ts). npm only emits a low-visibility npm warn install-scripts ... blocked because they are not covered by allowScripts — nothing in pinnedRuntime.ts surfaces or checks for this, so the install reports success (sentinel written, t3code.service starts) with a permanently broken PTY backend.
From there:
apps/server/src/terminal/NodePtyAdapter.ts'smakethrowsNodePtyModuleLoadErroron every PTY attempt. Provider health checks (Claude Agent CLI, Grok CLI, etc.) go through a PTY and failed on every launch witherrorTag: 'PlatformError'.- The systemd unit (
Restart=always,RestartSec=5) crash-looped roughly every 6 seconds as the child process kept dying. Each restart cycle also tore down and re-established the relay tunnel (CloudManagedEndpointRuntime.reconcileConfig/AgentAwarenessRelay) — visible as repeatingRelay client process started; waiting for tunnel connection→Relay client stoppedpairs inboot-service.log, instead of a single stable connection. After 6 restarts insideStartLimitIntervalSec=300, systemd gave up:t3code.servicesat infailed (start-limit-hit). - The desktop app runs its own separate local server instance (
server.mode: "desktop") that never went through this broken pinned-runtime install, so it kept working — which is why the background service appeared to depend on the desktop app being open.desktop.trace.ndjsonhad zeroNodePtyModuleLoadErroroccurrences the whole session, vs. 37 inboot-service.log.
This is a variant of #2621 / #6012 (missing Linux prebuild forces a local compile) but with a different trigger for the compile never happening: not a missing toolchain (this machine had make/gcc/g++/python3 present throughout), but npm's allow-scripts policy silently no-op'ing the install script with only a warning, which pinnedRuntime.ts never checks for.
Steps to reproduce
- On Linux, set a restrictive
allow-scriptspolicy in~/.npmrcthat does not covernode-ptyormsgpackr-extract, e.g.:allow-scripts=some-other-package - Install or update T3 Code so
ensurePinnedRuntimeInstalledrunsnpm install --prefix <staging> --no-fund --no-audit t3@<version>. - The install exits 0 and the sentinel gets written, but
node_modules/node-ptyhas nobuild/and noprebuilds/linux-x64/— theinstallscript was silently skipped (npm warn install-scripts ... blocked because they are not covered by allowScripts). - Start
t3code.service. It crash-loops onNodePtyModuleLoadErroreveryRestartSecinterval untilStartLimitBurstis hit and the unit fails permanently. Provider health checks fail every launch. Relay tunnel connects/disconnects in lockstep with the crash loop.
Version
0.0.33 (also confirmed still present on main as of this report — node-pty is unchanged in apps/server/package.json, still a plain dependencies entry with no Linux prebuild vendoring or allow-scripts detection)
Environment
Linux x64 (Arch, kernel 7.1.8-arch1-3), Node v26.7.0, npm 12.0.2, systemd user service (t3code.service)
Evidence
# boot-service.log — repeating crash loop
[20:30:50.997] WARN (#90): Claude Agent CLI health check failed.
{ errorTag: 'PlatformError' }
[20:30:51.005] WARN (#106): Grok CLI health check failed.
{ errorTag: 'PlatformError' }
[20:30:51.029] ERROR (#5): NodePtyModuleLoadError: Failed to load node-pty for linux-x64.
at catch (.../node_modules/t3/dist/NodePtyAdapter-DVcUyBhi.mjs:89:21)
[cause]: Error: Failed to load native module: pty.node, checked: build/Release, build/Debug, prebuilds/linux-x64:
Error: Cannot find module './prebuilds/linux-x64//pty.node'
[service-launcher] Active child exited unexpectedly (1).
# ...repeats every ~6-7s, 37 times total before systemd gives up
# systemctl --user status t3code.service
Active: failed (Result: start-limit-hit)
Aug 18 22:13:42 desktop systemd[717]: Start request repeated too quickly.
Aug 18 22:13:42 desktop systemd[717]: t3code.service: Failed with result 'start-limit-hit'.
# relay cycling in lockstep with the crash loop (boot-service.log)
[21:00:19.389] INFO (#62): Relay client process started; waiting for tunnel connection
[21:00:20.094] INFO (#182): Relay client stopped
[21:00:26.619] INFO (#62): Relay client process started; waiting for tunnel connection
[21:00:27.461] INFO (#182): Relay client stopped
# ...repeats
# npm install-scripts ls --prefix runtime/versions/0.0.33
2 packages have install scripts blocked because they are not covered by allowScripts:
msgpackr-extract@3.0.4 (install: node-gyp rebuild)
node-pty@1.1.0 (install: node-gyp rebuild)
Related issues
- bug: npx t3 fails silently on Linux (no Linux prebuilt for node-pty) #2621 (closed, but comments as recent as today show it reproducing on 0.0.33 with the identical stack trace) — missing Linux prebuild for
node-pty, forces a localnode-gypcompile. - Remote server update fails on Linux when node-pty must compile (no linux-x64 prebuild; opaque error) #6012 (open) — same missing-prebuild gap causing opaque failures on remote/self-update. Not a duplicate: that report's compile failed due to a missing
g++-11package; this report's compile was silently skipped entirely by npm'sallow-scriptspolicy, with a full toolchain present and available the whole time. Different trigger, same missing-prebuild root cause underneath, and neither report's fix (documenting compiler prerequisites) would have caught this one, since no compiler was ever invoked here.
Fix applied or workaround
On the affected machine:
cd ~/.t3/runtime/versions/0.0.33
npm install-scripts approve node-pty
npm install-scripts approve msgpackr-extract
npm rebuild node-pty msgpackr-extract
systemctl --user reset-failed t3code.service
systemctl --user restart t3code.serviceThis writes an allowScripts block into that runtime's own package.json (scoped to just this pinned runtime, the user's global ~/.npmrc policy was left untouched), lets node-gyp rebuild run for real, and it compiled cleanly with the toolchain already on the machine. Service has been stable since (relay tunnel connected, no PTY errors, no restarts).
Suggested upstream fixes:
ensurePinnedRuntimeInstalled(apps/server/src/cloud/pinnedRuntime.ts) could checknpm install-scripts ls(or parse stderr for theblocked because they are not covered by allowScriptswarning) after install and surface it as an actionablePinnedRuntimeInstallError, instead of writing the success sentinel over a silently-broken native module.- Ship a Linux prebuild for
node-pty(as suggested in bug: npx t3 fails silently on Linux (no Linux prebuilt for node-pty) #2621) so the install script has nothing to skip or compile in the common case. NodePtyAdapter.make's failure currently just crash-loops the whole process (viaEffect.orDie); consider degrading providers that need a PTY instead of taking the whole background service down, so a broken PTY backend doesn't also kill the relay connection.
Filed by
Claude (Sonnet 5) via t3 triage
Activity
phob commentedon Aug 19, 2026
Confirmed this also affects a direct
npx t3@nightly serveinstallation under npm 12, without intentionally configuring a restrictive package-specific allowlist.Environment:
0.0.34-nightly.20260819.11336.18.39-1-ltsv26.4.012.0.1npx t3@nightly serveThe npm debug log shows npm exiting successfully after warning:
The resulting
node-ptyinstallation contained Darwin and Windows prebuilds, but neitherprebuilds/linux-x64/pty.nodenorbuild/Release/pty.node. T3 then failed immediately with:This was also present in the previously installed T3 Code
0.0.33runtime.The following cache-scoped workaround succeeded:
Afterward,
node-pty/build/Release/pty.nodeexisted,npm install-scripts lsreported no unreviewed scripts, andnpx t3@nightly servereached the ready state successfully.This suggests npm 12 users may hit the issue during ordinary
npxuse, not only when they have deliberately added a restrictiveallow-scriptsentry.Investigated and drafted by OpenAI Codex (GPT-5) via
t3 triage.imadtg commentedon Aug 21, 2026
Hit by the same bug today; adding evidence that no custom
allow-scriptsconfig is needed — npm 12's default policy blocks the scripts.Environment
0.0.34-nightly.20260821.1151, installed/launched viabunx t3@nightly connect(systemd user background service)allow-scriptsorignore-scriptsconfig anywhere (~/.npmrccontains only registry auth + one scope line)What happened
t3code.servicecrash-looped: every start died after ~5.7s withNodePtyModuleLoadError: Failed to load node-pty for linux-x64(loader checkedbuild/Release,build/Debug,prebuilds/linux-x64; the installednode-pty@1.1.0had only darwin/win32 prebuilds and no local build). After 5 failures within 300s systemd gave up:Result=start-limit-hit. app.t3.codes sat on "reconnecting to this machine" indefinitely.warn install-scripts 2 packages had install scripts blocked because they are not covered by allowScripts: msgpackr-extract@3.0.4, node-pty@1.1.0 ...followed by a clean exit 0.build/Release/pty.node. The trigger was the npm 11 → 12 update between installs, not any t3 change.Why it slips through:
installPinnedRuntimetrusts npm's exit code and writes the.install-completesentinel; boot-service validation only runst3 --version, which never loads node-pty — so a runtime with no working terminal module is marked healthy until the server boots and dies.Workaround that fixed it here, for anyone landing here from a search:
Service returned to
active (running)with a freshly compiledpty.node.Possible fix directions: pass
--allow-scripts=node-pty,msgpackr-extract(or persist the config) during the pinned-runtime install, and/or make post-install validation actually load node-pty rather than trusting--version.Diagnosed via
t3 triage(opencode agent, model: ox-alpha).npx t3@latestcrashes on Linux: npm blocks node-pty's install script so pty.node never gets built #7847 as a duplicate of this issueProject516 commentedon Aug 27, 2026
Confirming this reproduces on a different platform/config from the original report, and that the default npm 12 behavior (not just a custom
allow-scripts=entry) triggers it on every managed-runtime auto-update.Additional observation — Raspberry Pi / default npm 12 empty allowlist:
ignore-scripts=false, no.npmrcat time of failure), install t3 and runt3 service install, or let an existing install auto-update its managed runtime (observed 0.0.33 → 0.0.34 → 0.0.35).systemctl --user status t3code.servicecrash-loops, then hitsStartLimitBurst(Start request repeated too quickly) and stops.~/.t3/runtime/versions/<new>/node_modules/node-pty/has nobuild/andprebuilds/contains onlydarwin-arm64,darwin-x64,win32-arm64,win32-x64. Thenode-pty@1.1.0tarball ships no Linux prebuilds at all.~/.t3/userdata/logs/boot-service.logshows:Per-version state on one machine:
build/Release/pty.nodeWhy the install is marked successful:
installPinnedRuntimeinapps/server/src/cloud/pinnedRuntime.tsruns plainnpm install --prefix <stagingDir> --no-fund --no-audit t3@<version>with no--allow-scripts. On npm 12 this silently skipsnode-pty'snode scripts/prebuild.js || node-gyp rebuild, npm still exits 0, and the.install-completesentinel is written. The launcher never retries. This is the same pattern already handled inapps/server/src/provider/providerMaintenance.ts, which passes--allow-scripts=<pkg>with the comment: "npm 12 blocks install scripts by default (empty allow-scripts allowlist) and still exits 0, so a package whose postinstall finishes the install is left broken while the update reports success." The pinned-runtime installer does not do the same.The earlier comments on this issue cover a restrictive
allow-scripts=other-pkgconfig and a directnpxrepro. This shows the bug also hits Linux users with no customallowScriptsconfig at all, on the managed-runtime path (t3 service install+ auto-update), and recurs on every version bump until repaired.Workaround per-version (same Node the service uses):
Permanent user-side mitigation that fixed future updates here: add
allow-scripts=node-pty,msgpackr-extractto~/.npmrc. The plainnpm installininstallPinnedRuntimethen compiles correctly.Suggested fix (to avoid a new issue): in
installPinnedRuntimeadd--allow-scripts=node-pty,msgpackr-extractto the npm args, mirroringproviderMaintenance.ts, and optionally verifynode_modules/node-pty/build/Release/pty.nodeexists before writing.install-completeso a broken install fails loudly and retries. Affects all Linux on npm 12 (arm64 and x64, node-pty has no Linux prebuilds for either).Environment for this repro: Raspberry Pi 5 (aarch64), Debian trixie, Node v26.5.0 (nvm), npm 12.0.2, systemd user service via
t3 service install, T3 Connect enabled.iam4x commentedon Sep 1, 2026
For anyone running into this made a tweet how to fix + a script for auto-updating and installing the service:
sheepbun commentedon Sep 6, 2026
Confirming this also hits the plain interactive
npx t3path (not just the managed background-service auto-update covered above), on the current stable release, with a completely default npm config.Environment
0.0.38(stable, not nightly)7.2.3-arch1-2(Arch)v26.8.1, npm12.0.2npx t3in an interactive terminalallow-scriptsorignore-scriptsconfig anywhere (~/.npmrcempty, no env overrides)What happened
Immediately after
npx t3starts the relay client, it crashes with:The npm debug log for the install confirms the same default-deny policy already described above — across the entire dependency tree (100+ packages), zero lifecycle scripts ran, and npm still exited 0.
node-pty'sprebuilds/only containeddarwin-arm64,darwin-x64,win32-arm64,win32-x64— nobuild/directory at all.This reproduces the same root cause as the comments above but confirms it's not limited to nightlies or the managed-service path — anyone on npm 12 running the documented
npx t3quick-start command hits this on a completely stock config, with the currently-published stable release.Workaround used (same as above, scoped to the npx cache dir this time):
This compiled
build/Release/pty.nodesuccessfully (build tools were already present) andnpx t3then ran cleanly.Given this affects the plain documented quick-start command on a default npm 12 install, it might be worth the README/quick-start explicitly calling this out (or T3 wrapping the npx bootstrap to pass
--allow-scripts=node-pty,msgpackr-extractitself) until node-pty ships a Linux prebuild or npm's allow-scripts UX improves.Diagnosed via
t3 triage(Claude Sonnet 5).juliusmarminge commentedon Sep 14, 2026
Fixed by the archive-only pinned runtime path on main (#11510, building on #11316 / #11317).
Managed runtimes no longer install from npm, so the npm 12
allow-scripts/ missingnode-ptynative failure mode forruntime/versions/...is gone. Archives ship the native tree already built.