Node.js OOM crash after ~37 min — 31,965 leaked async libuv handles (SEA ignores NODE_OPTIONS)
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Shell
- Star
- 11.2k
- Fork
- 1.9k
- Merge trung bình
- 14 giờ 16 phút
- Pull request đã merge (30 ngày)
- 6
Mô tả
Bug: Node.js OOM crash after ~37 minutes — 31,965 leaked async libuv handles
Version: 1.0.82
Platform: Linux x86_64 (Amazon EC2, Node v24.20.0 embedded in SEA)
Observed: 2026-09-01, crash at ~37 min session uptime
Summary
Every session crashes with FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory after roughly 30–60 minutes of use. The crash is caused by a monotonic leak of async libuv handles (~32k by crash time), not by conversation-context growth. /compact and increasing NODE_OPTIONS (which the SEA ignores) do not prevent the crash.
Heap report metrics (from report.20260901.153402.2817455.0.001.json)
| Metric | Value |
|---|---|
| Session uptime at crash | 2,203 s (~37 min) |
old_space used / capacity |
3,899 MB / 3,899 MB (100% saturated) |
externalMemory |
36 MB |
mallocedMemory |
~1 MB |
memoryLimit |
4,298 MB (~4.0 GB) |
| RSS at crash | 4,634 MB |
GC current mu |
0.173 (thrashing — normally >0.5 is healthy) |
| CPU at crash | ~292% (GC threads saturating 3 cores) |
Growth rate: ~100 MB/min of old_space objects throughout the session.
libuv handle leak
At crash time, the report shows:
libuv handle count: 31,996
type=async, is_active=true, is_referenced=false: 31,965
type=signal, is_active=true: 19
- 31,965 active-but-unreferenced
asynchandles — every one holds a JS closure/context in old_space. With no detail string (details: ""), these are likelyAsyncResource/AsyncLocalStoragecontexts that were created but never destroyed. - 19 active signal handles (vs. the ~17 signals that exist): duplicate SIGABRT, SIGIO registrations suggest repeated
process.on('signal', ...)calls without correspondingremoveListener— consistent with code that re-registers handlers per turn or per tool call.
The combination is a textbook listener/async-context leak: each model turn or tool execution allocates one or more async handles that are never cleaned up. After ~32k turns/tool calls the heap saturates.
Why NODE_OPTIONS and heap increases appear to help but don't
The copilot binary is a Node Single Executable Application (SEA) (NODE_SEA_BLOB present; 159 MB on disk). SEAs ignore NODE_OPTIONS entirely — empirically verified: NODE_OPTIONS="--this-is-not-a-real-flag" copilot --version exits 0 with no error.
The commandLine in the crash report confirms only Copilot's own flags are applied:
['copilot','--no-warnings','--report-on-fatalerror','--optimize-for-size','--expose-gc','copilot']
Increasing the (ignored) NODE_OPTIONS heap size is a placebo — the 4 GB cap is always in effect.
Reproduction pattern
The crash is reproducible by running a session of moderate length (>30 min) with regular tool calls (bash, view, grep). The leak appears to be per tool execution or per model turn rather than per session.
The --report-on-fatalerror flag already embedded in the binary's commandLine will produce a report.YYYYMMDD.*.json in the CWD on every crash — these reports contain full diagnostics.
Suggested investigation areas
AsyncResource/AsyncLocalStoragecontexts created per turn/tool that escape cleanup (search fornew AsyncResourceorAsyncLocalStorageusages inapp.js)process.on(signal, ...)calls withoutprocess.removeListener— each session re-registration without cleanup would explain the 19 duplicate signal handles- Any
setInterval/setTimeoutcreated per tool call that is neverclearInterval-ed — these register as async handles too
Workaround (until fixed)
Run the Copilot JS dist under a real node with a larger heap:
~/.nvm/versions/node/v24.20.0/bin/node \
--max-old-space-size=12288 \
--expose-gc --no-warnings --optimize-for-size \
~/.cache/copilot/pkg/linux-x64/1.0.82/index.js \
"$@"
This buys ~2 hours before the same leak exhausts 12 GB — same leak, bigger bucket.
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu bằng cách tái hiện một phiên với các lệnh gọi công cụ thông thường và kiểm tra báo cáo được tạo report.YYYYMMDD.*.json, sau đó đọc mã xử lý ngữ cảnh bất đồng bộ và tín hiệu trong app.js. Tìm các đường dẫn AsyncResource, AsyncLocalStorage, process.on, timer và cleanup đã được đề cập; được coi là hoàn tất khi số lượng handle không còn tăng đơn điệu và các phiên kéo dài tránh được OOM đã báo cáo.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript, node.js
- Lĩnh vực
- cli, devtools, performance
- Loại issue
- Lỗi
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Cần làm rõ
- Mức phù hợp với người mới
- 28/100