Pipe node into less or delta, then press q or j. Nothing happens until Enter. The pager isn’t broken, node just clobbered it on the way out.

less switches the terminal to raw mode so it can read keystrokes one at a time. Node’s I/O layer, libuv, snapshotted the terminal as cooked at startup. When node exits, an atexit hook (uv_tty_reset_mode()) blindly restores that snapshot, cooked mode, even though the pager downstream is still running and expects raw mode.

Two processes share one terminal, and node resets state it no longer owns.

Easiest fix, no code changes: redirect stdin/stderr at the shell level before the pipe starts.

claude --help < /dev/null 2> /dev/null | less
./parse_dumps --help < /dev/null 2> /dev/null | less

Without control over the invocation, or to bake the fix into the tool itself: give node nothing to reset. Redirect its stdin/stderr to /dev/null before it exits, so the reset call lands on a null device instead of the real tty:

if (!process.stdout.isTTY) {
  try { closeSync(0); openSync("/dev/null", "r"); } catch {}
  process.on("exit", () => {
    try { closeSync(2); openSync("/dev/null", "w"); } catch {}
  });
}

The real fix belongs upstream: uv_tty_reset_mode() should check tcgetpgrp() against its own process group before touching the terminal, and skip the reset if it’s no longer in the foreground.