Skip to content

node-pty native build silently skipped by npm allow-scripts policy, crash-loops the Linux background service #7475

Description

@sheepbun

What happened

Two symptoms that turned out to be the same root cause:

  1. 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.
  2. 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's make throws NodePtyModuleLoadError on every PTY attempt. Provider health checks (Claude Agent CLI, Grok CLI, etc.) go through a PTY and failed on every launch with errorTag: '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 repeating Relay client process started; waiting for tunnel connectionRelay client stopped pairs in boot-service.log, instead of a single stable connection. After 6 restarts inside StartLimitIntervalSec=300, systemd gave up: t3code.service sat in failed (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.ndjson had zero NodePtyModuleLoadError occurrences the whole session, vs. 37 in boot-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

  1. On Linux, set a restrictive allow-scripts policy in ~/.npmrc that does not cover node-pty or msgpackr-extract, e.g.:
    allow-scripts=some-other-package
    
  2. Install or update T3 Code so ensurePinnedRuntimeInstalled runs npm install --prefix <staging> --no-fund --no-audit t3@<version>.
  3. The install exits 0 and the sentinel gets written, but node_modules/node-pty has no build/ and no prebuilds/linux-x64/ — the install script was silently skipped (npm warn install-scripts ... blocked because they are not covered by allowScripts).
  4. Start t3code.service. It crash-loops on NodePtyModuleLoadError every RestartSec interval until StartLimitBurst is 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

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.service

This 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:

  1. ensurePinnedRuntimeInstalled (apps/server/src/cloud/pinnedRuntime.ts) could check npm install-scripts ls (or parse stderr for the blocked because they are not covered by allowScripts warning) after install and surface it as an actionable PinnedRuntimeInstallError, instead of writing the success sentinel over a silently-broken native module.
  2. 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.
  3. NodePtyAdapter.make's failure currently just crash-loops the whole process (via Effect.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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions