浏览器

NodeJS

针对 Node.js 运行时、打包与线上问题,给出可落地的诊断依据与修复规则。

它能做什么

面向 Node.js 服务、命令行工具与 npm 包的参考手册,覆盖异步、模块系统、流、内存与进程生命周期。内含症状到成因速查表、八条核心运维规则、退出码与错误码表、超时阶梯以及 Node 版本特性门槛。可用于编写或审查 Node 代码、排查线上进程、响应生产事故,以及加固关停与部署行为。明确不覆盖:纯浏览器 JavaScript、TypeScript 类型系统设计、Bun 与 Deno 运行时。

什么时候用它

  • 排查 Node 进程卡住、内存泄漏或无法退出的问题
  • 解决 ESM/CJS 互操作错误,如 ERR_REQUIRE_ESM 或 ERR_MODULE_NOT_FOUND
  • 加固 SIGTERM 与容器部署下的优雅关停,避免在途请求被丢弃
  • 定位代理或调度器背后的间歇性 502、EMFILE 与 OOMKill

技能文档

User preferences and memory live in ~/Clawic/data/nodejs/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/nodejs/ or ~/clawic/nodejs/), move it to ~/Clawic/data/nodejs/.

When To Use

  • Writing or reviewing Node services, CLIs, libraries, workers, or release scripts
  • Debugging a live process: hangs, crash loops, growing RSS, slow endpoints, import failures, unclean shutdown
  • Deciding the runtime shape of a package: ESM vs CJS, exports map, lockfile policy, native dependencies, publish surface
  • Production incidents: intermittent 502s behind a proxy, EMFILE, event loop stalls, containers OOMKilled overnight
  • Running TypeScript, tests, or child processes on Node — the runtime side, not the type system
  • Not for browser-only JavaScript, TypeScript type design, or the Bun/Deno runtimes (each has its own skill)

Quick Reference

SituationPlay
One core pinned, every request slows at onceEvent loop blocked. Record node --cpu-prof, read widest self-time frames, not deepest stacks → performance.md
Process finishes its work but never exitsAn open handle: server, socket, setInterval, worker, or pool. process.getActiveResourcesInfo() names it → debug.md
RSS climbs while heapUsed stays flatBuffers/native memory, not a JS leak — heap snapshots will show nothing → performance.md
Intermittent 502/504 behind ALB or nginxkeepAliveTimeout below the proxy's idle timeout (rule 5) → http.md
ERR_REQUIRE_ESM, ERR_MODULE_NOT_FOUND, __dirname is not definedModule-format mismatch or a missing file extension → modules.md
EADDRINUSE on restartOld process still holding the port, or two workers binding it; in tests, bind port 0 → debug.md
EMFILE / ENFILEUnbounded concurrency or leaked descriptors (rule 4) → filesystem.md
Exit 134 with FATAL ERROR: ... heap out of memoryV8's heap ceiling, not the container's limit (rule 8) → performance.md
Memory grows during a copy, upload, or exportBackpressure ignored: write() returned false and nobody waited → streams.md
npm install works, npm ci failsLockfile out of sync with package.json (rule 6) → packages.md
Native module fails to build or load after an upgradeABI mismatch, missing toolchain, or musl vs glibc → runtime.md
Child process hangs, or its output is truncatedmaxBuffer exceeded, or nobody drains the stdio pipe → concurrency.md
A .ts file won't run, or imports resolve at build but not at runtimeType-stripping limits or moduleResolution mismatch → typescript.md
CLI crashes with EPIPE when piped into headstdout closed early; handle the error instead of letting it throw → cli.md
Suite green locally, red in CIPort collisions, fake timers, shared module state, file ordering → testing.md
Untrusted input reaching exec, a path join, or a regexInjection, traversal, ReDoS — the exact checks → security.md
SIGTERM drops in-flight requests on deployNo drain sequence, or node is PID 1 with no signal handler → production.md
Anything elseReproduce with the smallest script that still fails, then re-add flags, env vars, and imports one at a time

Depth on demand: debug.md symptom→cause chains · commands.md diagnostic toolkit · async.md event loop and promises · modules.md ESM/CJS · errors.md failure handling and shutdown · streams.md backpressure · http.md servers, clients, timeouts · performance.md profiling and leaks · concurrency.md workers, cluster, child processes · filesystem.md files, paths, descriptors · security.md hardening · testing.md suites and mocks · packages.md npm, lockfiles, publishing · typescript.md running TS on Node · runtime.md versions, flags, native modules · production.md deploy, config, logging, observability · cli.md command-line tools.

Core Rules

  1. Budget sync work at ~10 ms per slice. Max throughput per process ≈ 1000 ms ÷ block_ms: a 50 ms sync JSON.parse in a request handler caps that process near 20 req/s, and every concurrent request inherits the stall. Repeatedly over budget → worker_threads; one-off → partition with setImmediate (→ async.md).
  2. Handle operational errors, crash on programmer errors (Joyent doctrine). ECONNRESET, ENOENT, bad user input → handle at the call site. TypeError, undefined property → let it crash; the supervisor restarts a clean process. Catch-all recovery keeps corrupted state serving traffic.
  3. "Async" fs, dns.lookup, crypto, and zlib share 4 libuv threads (default UV_THREADPOOL_SIZE; max 1024). The 5th concurrent fs.promises.readFile queues behind the first 4. Slow "async" crypto/fs under load → raise the pool to roughly the number of concurrent slow calls, or move the work off the process.
  4. Cap concurrency explicitly. Promise.all(items.map(fetch)) over 10k items opens 10k sockets — EMFILE at the common 1024 fd soft limit. Batch or use a limiter; 8-32 concurrent per upstream host is a sane starting cap, raised on evidence: throughput climbing with flat latency = raise it; latency climbing = you found the ceiling.
  5. keepAliveTimeout must exceed the proxy's idle timeout. Node's default is 5 s; ALB's default idle is 60 s, so Node closes sockets the balancer just reused → intermittent 502s. Formula: keepAliveTimeout = proxy_idle + 5 s and headersTimeout = keepAliveTimeout + 1 s (ALB 60 s → 65 s / 66 s; nginx upstream keepalive_timeout 75s → 80 s / 81 s).
  6. Exact versions for apps, ranges for libraries; npm ci in anything automated. npm install rewrites the lockfile whenever package.json allows it — in CI that means testing an install nobody reviewed (→ packages.md).
  7. One process per container. Let the orchestrator scale replicas; cluster only on bare metal or VMs you own entirely — two schedulers fighting over the same cores obscures both.
  8. Set the heap ceiling below the memory limit. RSS = heap + buffers + stacks + native, so a default-sized heap under a container limit invites an OOMKill instead of a catchable heap error. Formula: --max-old-space-size = floor(0.75 × container_limit_MB) — 512 MB limit → 384; 2 GB → 1536. Exit 134 with FATAL ERROR: ... heap out of memory means you hit V8's ceiling; exit 137 means the kernel hit yours first.

Exit and Error Codes

Exit codes above 128 mean killed by signal code − 128; Node's own fatal codes sit below 10.

CodeMeaningFirst move
1Uncaught fatal exceptionRead the stack; an empty one means a non-Error was thrown (→ Traps)
3 / 5Internal JS parse failure / fatal V8 errorAlmost always a bad --flag or a corrupt install, not your code
7Exception inside an exception handlerYour uncaughtException hook itself threw
9Invalid argument to the node binaryFlag typo, or a flag this major doesn't support (→ runtime.md)
130SIGINT (128+2)Ctrl-C; clean unless a handler swallowed it
134SIGABRT (128+6)V8 fatal — usually heap OOM (rule 8) or a failed native assertion
137SIGKILL (128+9)Kernel or orchestrator OOM kill, or a stop timeout expiring (rule 8)
143SIGTERM (128+15)Normal stop; if requests were dropped, the drain sequence is missing (→ production.md)

Error codes worth recognizing on sight: EADDRINUSE (port held), EMFILE/ENFILE (descriptor limit, rule 4), ECONNRESET (peer closed mid-flight — operational, rule 2), EPIPE (downstream closed a pipe), ERR_REQUIRE_ESM and ERR_MODULE_NOT_FOUND (format or extension), ERR_UNHANDLED_REJECTION (rule 2), ERR_STRING_TOO_LONG (V8 caps string length in the hundreds of MB — stream instead of JSON.stringify), ERR_CHILD_PROCESS_STDIO_MAXBUFFER (child output over the buffer cap).

Timeout Ladder

Every timeout in the path must be ordered, or one layer kills a connection another layer still believes in. Each line reads strictly larger left to right:

client abort  <  upstream call timeout  <  inbound requestTimeout
proxy idle    <  keepAliveTimeout       <  headersTimeout
force-exit backstop  <  orchestrator kill window
  • Node HTTP server defaults (node >=18): keepAliveTimeout 5 s, headersTimeout 60 s, requestTimeout 300 s, server.timeout 0 (disabled). Only the first is short enough to bite by accident (rule 5).
  • Outbound calls need their own deadline — an upstream that never answers holds a socket, a descriptor, and a request slot. AbortSignal.timeout(ms), with retries × per-try timeout still under requestTimeout.
  • Shutdown backstop = orchestrator grace window − 5 s (Kubernetes terminationGracePeriodSeconds defaults to 30 s → force-exit at 25 s), so you exit on your own terms with logs flushed instead of by SIGKILL.

Version Gates

The canonical minimums this skill's guidance assumes. Check node -v before recommending anything above the user's line, and name the fallback when the target is older.

FeatureMinimum
Unhandled rejection terminates the process (default)15
new Error(msg, { cause })16.9
AbortSignal.timeout(), process.getActiveResourcesInfo()17.3
Global fetch/undici (stable from 21)18
node:test runner stable20
--env-file=.env built in20.6
import.meta.dirname / import.meta.filename20.11
node --run , --watch stable22
require() of a synchronous ESM graph22.12
Running .ts by type stripping, unflagged24 (backported to 22.18)

Release cadence, so "supported" stays checkable instead of remembered: one new major every April and October; even majors enter LTS that October and get ~3 years total (6 months Current, 12 Active LTS, 18 Maintenance); odd majors die after ~6 months and never belong in production. Which majors are live today: runtime.md (dated).

Output Gates

Before shipping Node code, verify:

  • Any *Sync call, unbounded JSON.parse, or whole-array loop reachable from a request handler? (rule 1)
  • Every Promise.all bounded by something other than user input? (rule 4)
  • Every stream wired through pipeline() or carrying its own error handler — an unhandled stream error is a process crash?
  • Every outbound call carrying a timeout or AbortSignal, and every server timeout ordered per the ladder?
  • All process.env reads parsed and validated once at startup — they are strings or undefined, never numbers or booleans?
  • Shutdown path present: SIGTERM → fail readiness → stop accepting → drain → close pools → exit, in that order (→ errors.md)?
  • Heap ceiling set against the container limit (rule 8), and one process per container (rule 7)?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/nodejs/config.yaml.

VariableTypeDefaultEffect
package_managernpm | pnpm | yarn | bunnpmEvery install, lockfile, workspace, and CI command; which npm ci equivalent rule 6 names
module_systemesm | cjsesmImport syntax and extensions in all examples, type field guidance, which half of modules.md leads
node_targetnumber (major, >=20)24Which APIs the guidance may use without a fallback — read against the Version Gates table
test_runnernode | jest | vitestnodeTest, mock, and fake-timer idioms; the coverage and watch flags quoted in testing.md
deploy_targetcontainer | vm | serverless | paascontainerSignal and PID-1 handling, cluster vs replicas (rule 7), heap sizing (rule 8), shutdown window
ts_runnernone | strip | tsx | tscnoneWhether .ts guidance assumes type stripping, a loader, or a build step (typescript.md)

Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied:

  • Tooling — script runner (node --run vs package-manager scripts), linter/formatter stack, profiler of choice — affects the shape of every command example
  • Conventions — extensions (.mjs/.cjs vs a type field), error-class taxonomy, script names, folder layout, log field names
  • Platform — OS (Windows path and spawn caveats), architecture (arm64 prebuilds), base image (musl vs glibc), registry mirror
  • Thresholds — concurrency cap per upstream (rule 4), heap fraction (rule 8), timeout values, stream highWaterMark
  • Risk posture — crash-vs-catch aggressiveness (rule 2), --ignore-scripts on install, automatic major bumps, permission model
  • Output format — log shape (JSON vs pretty), verbosity, how much error detail reaches clients
  • Work order — test-first vs code-first, profile-before-optimize, review gates before publish
  • Integrations — registry, observability vendor, CI provider, process supervisor
  • Constraints — banned dependencies, license policy, no-native-modules, offline or air-gapped installs
  • Cadence — dependency update rhythm, Node upgrade rhythm against the April/October cadence

Traps

TrapWhy it failsDo instead
Unhandled promise rejectionTerminates the process by default (node >=15).catch() or try/catch on every await chain
.pipe() without error wiringErrors don't cross pipes; the source leaks, then crashesstream.pipeline()
Forgotten await inside try/catchThe rejection skips the catch and surfaces later, or neverLint with no-floating-promises
exports = x in CJSRebinds a local alias; module.exports is unchangedmodule.exports = x
__dirname in ESMDoesn't exist thereimport.meta.dirname, else fileURLToPath(import.meta.url)
Mutating a require()d objectModule cache: every consumer sees the mutationExport factories, or freeze exports
process.env.PORT used as a numberEnv values are strings ("3000") or undefinedParse and validate the whole env once at startup
Listeners added per requestAccumulate to MaxListenersExceededWarning — a leak signal, not a limitonce(), or remove on completion
Raising the max-listeners limit to silence that warningHides the leak that produced it; memory keeps climbingFind who adds a listener per event and remove it
Throwing non-Error valuesNo stack; instanceof Error checks fail; exit 1 with an empty tracethrow new Error(msg, { cause })
node:vm as a sandboxNot a security boundary — its own docs say soIsolate untrusted code at process or container level
npm audit fix --force on a red reportApplies major bumps that break the app to silence findings in code that may never runTriage by reachability, pin with overrides (→ packages.md)
node index.js as PID 1 in a containerPID 1 gets no default signal handlers: SIGTERM is ignored and every stop burns the full grace windowRegister a SIGTERM handler, or run an init (→ production.md)

Where Experts Disagree

  • Crash-and-restart vs catch-and-continue. Crash when a supervisor restarts you and in-process state may be corrupt; continue only for operational errors scoped to a single request. The boundary question: can you name exactly what stayed consistent?
  • Dual CJS+ESM packages vs ESM-only. Dual carries the two-instance hazard (instanceof fails across copies, module state duplicates); ESM-only is viable once consumers are ESM or run node >=22.12, where require() of a sync ESM graph works.
  • node:test vs Jest vs Vitest. node:test for new libraries — no transform layer, native ESM, no hoisting magic; Jest for existing suites with heavy matcher and snapshot use; Vitest where the project already runs a Vite pipeline. Migrating a green suite for aesthetics is a net loss.
  • TypeScript at runtime vs a build step. Type stripping deletes the toolchain for apps deployed from source; a tsc build stays correct for published libraries that must ship .d.ts and support older consumers.

More Clawic skills, get them at https://clawic.com/skills/nodejs (install if the user confirms):

  • javascript — language-level semantics: coercion, closures, dates, regex
  • typescript — type-system design, tsconfig strictness, declaration files
  • docker — containerizing the service, image layers, resource limits
  • api — HTTP API design, versioning, and contracts above the runtime

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/nodejs.

相关技能

围绕运行语义、异步行为与特性版本下限,为 Node 与浏览器场景调试与编写 JavaScript。

作者 Iván135 次安装6 星标

解读 TypeScript 类型错误,设计 API、tsconfig 与 .d.ts 的类型方案。

185 次安装6 星标

调试、编写和审查 Go 代码,覆盖 goroutine、错误处理、模块与标准库的实践指导。

82 次安装3 星标

按规则构建、调试和审查 React 应用,覆盖组件、状态、表单、性能与 React 19 特性。

245 次安装3 星标

按文档化的 Python 规则手册和固定检查清单排查、审阅与编写代码。

156 次安装6 星标

编写并审查 nginx 配置,排查反向代理下的 502/504/413/403 与重定向循环。

132 次安装5 星标