Summary
process.ppid in bun returns a cached value from startup and never updates, even after the parent process dies and the child is reparented to PID 1 (init). In Node.js, process.ppid is a live getter that reflects the current parent. This difference breaks orphan detection patterns that are common in process supervisors and MCP plugins.
Reproduction
# Terminal 1: Start a parent that spawns bun, then kill the parent
bash -c 'bun -e "
setInterval(() => {
const fs = require(\"fs\");
const realPpid = parseInt(fs.readFileSync(\"/proc/\" + process.pid + \"/stat\", \"utf8\").split(\" \")[3]);
console.log(\"process.ppid=\" + process.ppid + \" real_ppid=\" + realPpid);
}, 2000);
" &
BUN_PID=$!
echo "Parent shell PID=$$, bun PID=$BUN_PID"
sleep 3
echo "Killing parent shell now..."
kill $$'
# After the parent shell dies, observe bun's output:
# process.ppid=<DEAD_PID> real_ppid=1
# process.ppid=<DEAD_PID> real_ppid=1
# ...
# process.ppid never changes to 1, even though /proc confirms reparenting
Expected behavior (matches Node.js)
After the parent dies:
process.ppid=1 real_ppid=1
Actual behavior (bun 1.3.12)
After the parent dies:
process.ppid=<original_dead_pid> real_ppid=1
process.ppid is frozen at the value from process startup and never updates.
Environment
- bun: 1.3.12 (also confirmed on 1.3.11)
- OS: Ubuntu 24.04, kernel 6.8.0-107-generic x86_64
- Node.js comparison: Node 20+ correctly returns updated ppid
Impact
This breaks any orphan-detection logic that relies on process.ppid === 1 to determine if the parent has died. Common pattern:
// Works in Node.js, broken in bun:
setInterval(() => {
if (process.ppid === 1) {
console.log('Parent died, shutting down gracefully');
process.exit(0);
}
}, 5000);
Real-world impact: The Claude Code Telegram MCP plugin uses this pattern for zombie process detection. Because bun never updates process.ppid, orphaned plugin processes persist indefinitely, holding resources (in this case, a Telegram Bot API long-poll that causes 409 Conflict for all subsequent instances).
Workaround
Read /proc/<pid>/stat directly to get the real ppid from the kernel:
import { readFileSync } from 'fs';
function getRealPpid(pid: number): number {
try {
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
return parseInt(stat.split(' ')[3], 10);
} catch {
return -1;
}
}
This works but is Linux-only and shouldn't be necessary — process.ppid should just work.
Suggested Fix
process.ppid should be implemented as a getter that calls getppid(2) on each access (same as Node.js), rather than caching the value at startup.
Node.js implementation for reference: https://github.com/nodejs/node/blob/main/src/node_process_methods.cc (uses uv_os_getppid() which calls getppid(2))
Summary
process.ppidin bun returns a cached value from startup and never updates, even after the parent process dies and the child is reparented to PID 1 (init). In Node.js,process.ppidis a live getter that reflects the current parent. This difference breaks orphan detection patterns that are common in process supervisors and MCP plugins.Reproduction
Expected behavior (matches Node.js)
After the parent dies:
Actual behavior (bun 1.3.12)
After the parent dies:
process.ppidis frozen at the value from process startup and never updates.Environment
Impact
This breaks any orphan-detection logic that relies on
process.ppid === 1to determine if the parent has died. Common pattern:Real-world impact: The Claude Code Telegram MCP plugin uses this pattern for zombie process detection. Because bun never updates
process.ppid, orphaned plugin processes persist indefinitely, holding resources (in this case, a Telegram Bot API long-poll that causes 409 Conflict for all subsequent instances).Workaround
Read
/proc/<pid>/statdirectly to get the real ppid from the kernel:This works but is Linux-only and shouldn't be necessary —
process.ppidshould just work.Suggested Fix
process.ppidshould be implemented as a getter that callsgetppid(2)on each access (same as Node.js), rather than caching the value at startup.Node.js implementation for reference: https://github.com/nodejs/node/blob/main/src/node_process_methods.cc (uses
uv_os_getppid()which callsgetppid(2))