Skip to content

Conversation

@robobun
Copy link
Collaborator

@robobun robobun commented Dec 3, 2025

Summary

Adds support for stdin: "pty", stdout: "pty", and stderr: "pty" options in Bun.spawn(). This allows spawned processes to run in a pseudo-terminal, making process.stdout.isTTY === true in the child process.

This is useful for:

  • Getting colored output from CLI tools that detect TTY (git, grep, ls, etc.)
  • Running interactive programs that require a terminal
  • Testing terminal-dependent behavior

Usage

Basic usage

const proc = Bun.spawn(["ls", "--color=auto"], {
  stdout: "pty",
});
const output = await proc.stdout.text();
// output includes ANSI color codes

Custom terminal dimensions

const proc = Bun.spawn({
  cmd: ["bun", "-e", "console.log(process.stdout.columns, process.stdout.rows)"],
  stdout: { type: "pty", width: 120, height: 40 },
});
// Child sees 120 columns and 40 rows

Multiple PTY streams

const proc = Bun.spawn({
  cmd: ["bun", "-e", "console.log(process.stdin.isTTY, process.stdout.isTTY)"],
  stdin: "pty",
  stdout: "pty",
});
// Both stdin and stdout report isTTY === true

Implementation Details

  • Uses openpty() to create a PTY master/slave pair
  • Child process gets the slave side; parent gets the master for I/O
  • When multiple stdios use PTY, they share the same PTY pair
  • Each stdio gets a dup()'d master FD for independent epoll registration
  • Handles EIO from PTY master as normal EOF (occurs when slave closes)
  • Default terminal size: 80 columns x 24 rows (standard VT100)

Platform Support

Platform Support
macOS ✅ Full PTY support
Linux ✅ Full PTY support
Windows ⚠️ Falls back to "pipe" (no TTY semantics)

Limitations

  • Not supported with spawnSync: PTY requires async I/O. Throws an error.
  • No dynamic resize: Terminal dimensions are set at spawn time only.
  • Line endings: PTY converts \n to \r\n on output (standard terminal behavior).

Test Plan

  • stdout: "pty" makes process.stdout.isTTY true
  • stderr: "pty" makes process.stderr.isTTY true
  • stdin: "pty" only (stdout/stderr as pipe)
  • Multiple PTY streams (stdin + stdout + stderr)
  • Object syntax with custom width/height
  • ANSI color output detection
  • Multiple concurrent PTY spawns
  • spawnSync throws error for PTY
  • All 10 tests passing

Changes

  • src/sys.zig: Add openpty() syscall wrapper
  • src/bun.js/api/bun/process.zig: PTY creation in spawn
  • src/bun.js/api/bun/spawn/stdio.zig: Parse "pty" option
  • src/bun.js/api/bun/subprocess/: PTY handling for Readable/Writable
  • src/io/PipeReader.zig: Handle EIO as EOF for PTY
  • packages/bun-types/bun.d.ts: Add "pty" to types
  • docs/runtime/child-process.mdx: Comprehensive documentation

🤖 Generated with Claude Code

Claude Bot and others added 4 commits December 3, 2025 09:08
Add support for `stdin: "pty"`, `stdout: "pty"`, and `stderr: "pty"` options
in `Bun.spawn()`. This allows spawned processes to see `process.stdout.isTTY === true`.

Key implementation details:
- PTY creation via openpty/forkpty syscalls in sys.zig
- When multiple stdios use PTY, they share the same master FD
- Handle epoll EEXIST gracefully when FD is already registered
- Timer-based polling fallback for shared PTY FDs
- EIO treated as normal EOF when PTY slave closes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused uws import from PipeReader.zig
- Remove unused PTY helper functions (setWinSize, getWinSize, setControllingTerminal)
- Remove unused ioctl constants (TIOCSWINSZ, TIOCSCTTY, TIOCGWINSZ)
- Change spawn_bindings log visibility back to hidden
- Add "pty" to TypeScript types in bun.d.ts
- Add PTY documentation to docs/runtime/child-process.mdx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add tests for:
- stderr: 'pty' only
- stdin: 'pty' only (stdout/stderr not PTY)
- PTY object syntax with custom width/height dimensions
- ANSI color output detection
- Multiple concurrent PTY spawns
- Windows error handling (skipped on non-Windows)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add comprehensive PTY documentation with examples:
  - Basic usage
  - Multiple PTY streams (stdin/stdout/stderr)
  - Custom terminal dimensions (width/height)
  - Colored output from git, grep, etc.
  - Platform support table
  - Limitations section

- Make PTY fall back to pipe on Windows (instead of error)
- Add spawnSync error for PTY (not supported)
- Add tests for spawnSync PTY error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@robobun robobun requested a review from alii as a code owner December 3, 2025 10:45
@github-actions github-actions bot added the claude label Dec 3, 2025
@robobun
Copy link
Collaborator Author

robobun commented Dec 3, 2025

Updated 4:29 AM PT - Dec 3rd, 2025

❌ Your commit c31f2ef4 has 5 failures in Build #32867 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 25319

That installs a local version of the PR into your bun-25319 executable, so you can run:

bun-25319 --bun

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

Adds PTY support across Bun's spawn stack: new .pty stdio option and types, POSIX openpty integration, PTY-aware stdio parsing and wiring, reader/writer PTY handling (is_pty + EIO-as-EOF), spawn logging, and tests; spawnSync disallows PTY.

Changes

Cohort / File(s) Summary
Docs & Type defs
docs/runtime/child-process.mdx, packages/bun-types/bun.d.ts
Document PTY usage and add "pty" to public Readable and Writable spawn option unions and related typings.
Platform sys & low-level API
src/sys.zig
Add openpty, PtyPair, and WinSize to support PTY creation and window sizing.
Spawn stdio parsing & config
src/bun.js/api/bun/spawn/stdio.zig, src/bun.js/api/bun/process.zig
Parse .pty stdio (string or object), introduce PtyOptions/PtyConfig (width/height), map to POSIX pty and Windows pipe fallback, prohibit PTY with spawnSync, and expose pty_master in spawn result.
Subprocess readers/writers plumbing
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig, src/bun.js/api/bun/subprocess/Readable.zig, src/bun.js/api/bun/subprocess/Writable.zig, src/io/PipeReader.zig
Add is_pty flag, createForPty, PTY-aware initialization for readers/writers, treat PTY EIO as EOF, and avoid socket recv semantics for PTY masters.
Spawn bindings, glue & shell
src/bun.js/api/bun/js_bun_spawn_bindings.zig, src/bun.js/api/bun/subprocess.zig, src/shell/subproc.zig
Add runtime spawn/stdout logs; wire PTY into POSIX spawn flow, duplicate master FDs for parent use and cleanup, and add .pty variants to shell subprocess unions.
Tests
test/js/bun/spawn/spawn-pty.test.ts
Add tests covering PTY behavior: isTTY, stdin/stdout PTY combos, dimensions, colored output, concurrency, and spawnSync error cases.

Possibly related PRs

Suggested reviewers

  • pfgithub

Pre-merge checks

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature addition: PTY support for stdin, stdout, and stderr in the spawn API.
Description check ✅ Passed The description covers all template sections with comprehensive detail: a summary of the feature, usage examples, implementation details, platform support, limitations, and a test plan with verified results.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 064b1d3 and c31f2ef.

📒 Files selected for processing (1)
  • src/bun.js/api/bun/process.zig (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/bun.js/api/bun/process.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Use bun.Output.scoped(.${SCOPE}, .hidden) for creating debug logs in Zig code

Implement core functionality in Zig, typically in its own directory in src/

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Be careful with memory management in Zig code - use defer for cleanup with allocators

Files:

  • src/bun.js/api/bun/process.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.zig: Expose generated bindings in Zig structs using pub const js = JSC.Codegen.JS<ClassName> with trait conversion methods: toJS, fromJS, and fromJSDirect
Use consistent parameter name globalObject instead of ctx in Zig constructor and method implementations
Use bun.JSError!JSValue return type for Zig methods and constructors to enable proper error handling and exception propagation
Implement resource cleanup using deinit() method that releases resources, followed by finalize() called by the GC that invokes deinit() and frees the pointer
Use JSC.markBinding(@src()) in finalize methods for debugging purposes before calling deinit()
For methods returning cached properties in Zig, declare external C++ functions using extern fn and callconv(JSC.conv) calling convention
Implement getter functions with naming pattern get<PropertyName> in Zig that accept this and globalObject parameters and return JSC.JSValue
Access JavaScript CallFrame arguments using callFrame.argument(i), check argument count with callFrame.argumentCount(), and get this with callFrame.thisValue()
For reference-counted objects, use .deref() in finalize instead of destroy() to release references to other JS objects

Files:

  • src/bun.js/api/bun/process.zig
🧠 Learnings (5)
📓 Common learnings
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • src/bun.js/api/bun/process.zig
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • src/bun.js/api/bun/process.zig
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr

Applied to files:

  • src/bun.js/api/bun/process.zig
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • src/bun.js/api/bun/process.zig
🔇 Additional comments (6)
src/bun.js/api/bun/process.zig (6)

1007-1013: LGTM!

The PtyConfig struct with standard 80×24 default dimensions is a clean addition.


1114-1116: LGTM!

Clear documentation for the PTY master FD lifecycle.


1121-1130: LGTM!

The close() method now has the correct signature (*PosixSpawnResult) and properly handles PTY master cleanup with null assignment to prevent double-close.


1317-1347: LGTM!

The single-PTY-pair approach with shared slave across multiple stdio is correct. The window size from the first .pty option applies to the shared PTY, which is the expected behavior.


1465-1487: LGTM!

The duped master FD is now correctly tracked in to_close_on_error (Line 1483), addressing the previous review concern. The stderr special-case when stdout also uses PTY is correct.


1534-1546: LGTM!

The duped master FD is now correctly added to both to_close_on_error and extra_fds, addressing the previous review concern about inconsistent handling.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/bun.js/api/bun/subprocess.zig (1)

284-291: Additional stdout debug logging is safe

The new log("getStdout: ...") call is purely diagnostic and doesn’t affect control flow or resource handling. It should be helpful when debugging PTY/stdout state without impacting behavior.

If it proves too chatty, you can later gate it behind Environment.isDebug or a dedicated debug flag.

packages/bun-types/bun.d.ts (1)

5334-5362: Type safety gap: spawnSync allows "pty" despite runtime error.

According to the PR summary, PTY is not supported with spawnSync and will throw an error at runtime. However, adding "pty" to the Readable and Writable unions means TypeScript will allow this code:

Bun.spawnSync({
  cmd: ["echo", "hello"],
  stdin: "pty"  // TypeScript allows this but it throws at runtime!
});

To fix this type safety issue, consider one of these approaches:

Option 1: Separate type unions

+type SyncReadable = Exclude<Readable, "pty">;
+type SyncWritable = Exclude<Writable, "pty">;

 interface SpawnSyncOptions<
-  In extends Writable,
-  Out extends Readable,
-  Err extends Readable
+  In extends SyncWritable,
+  Out extends SyncReadable,
+  Err extends SyncReadable
 > extends BaseOptions<In, Out, Err> {}

Option 2: Conditional types with never

 interface SpawnSyncOptions<
   In extends Writable,
   Out extends Readable,
   Err extends Readable
-> extends BaseOptions<In, Out, Err> {}
+> extends BaseOptions<In, Out, Err> {
+  stdin?: In extends "pty" ? never : In;
+  stdout?: Out extends "pty" ? never : Out;
+  stderr?: Err extends "pty" ? never : Err;
+  stdio?: In extends "pty" ? never : Out extends "pty" ? never : Err extends "pty" ? never : [In, Out, Err, ...Readable[]];
+}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e9e9324 and 6d4965c.

📒 Files selected for processing (13)
  • docs/runtime/child-process.mdx (4 hunks)
  • packages/bun-types/bun.d.ts (9 hunks)
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig (2 hunks)
  • src/bun.js/api/bun/process.zig (5 hunks)
  • src/bun.js/api/bun/spawn/stdio.zig (5 hunks)
  • src/bun.js/api/bun/subprocess.zig (1 hunks)
  • src/bun.js/api/bun/subprocess/Readable.zig (5 hunks)
  • src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig (4 hunks)
  • src/bun.js/api/bun/subprocess/Writable.zig (1 hunks)
  • src/io/PipeReader.zig (3 hunks)
  • src/shell/subproc.zig (4 hunks)
  • src/sys.zig (2 hunks)
  • test/js/bun/spawn/spawn-pty.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/shell/subproc.zig
  • src/sys.zig
  • src/bun.js/api/bun/subprocess.zig
  • src/io/PipeReader.zig
  • src/bun.js/api/bun/subprocess/Readable.zig
  • src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Use bun.Output.scoped(.${SCOPE}, .hidden) for creating debug logs in Zig code

Implement core functionality in Zig, typically in its own directory in src/

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Be careful with memory management in Zig code - use defer for cleanup with allocators

Files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/shell/subproc.zig
  • src/sys.zig
  • src/bun.js/api/bun/subprocess.zig
  • src/io/PipeReader.zig
  • src/bun.js/api/bun/subprocess/Readable.zig
  • src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
**/js_*.zig

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

**/js_*.zig: Implement JSYourFeature struct in a file like js_your_feature.zig to create JavaScript bindings for Zig functionality
Use bun.JSError!JSValue for proper error propagation in JavaScript bindings
Implement proper memory management with reference counting using ref()/deref() in JavaScript bindings
Always implement proper cleanup in deinit() and finalize() methods for JavaScript binding classes

Files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.zig: Expose generated bindings in Zig structs using pub const js = JSC.Codegen.JS<ClassName> with trait conversion methods: toJS, fromJS, and fromJSDirect
Use consistent parameter name globalObject instead of ctx in Zig constructor and method implementations
Use bun.JSError!JSValue return type for Zig methods and constructors to enable proper error handling and exception propagation
Implement resource cleanup using deinit() method that releases resources, followed by finalize() called by the GC that invokes deinit() and frees the pointer
Use JSC.markBinding(@src()) in finalize methods for debugging purposes before calling deinit()
For methods returning cached properties in Zig, declare external C++ functions using extern fn and callconv(JSC.conv) calling convention
Implement getter functions with naming pattern get<PropertyName> in Zig that accept this and globalObject parameters and return JSC.JSValue
Access JavaScript CallFrame arguments using callFrame.argument(i), check argument count with callFrame.argumentCount(), and get this with callFrame.thisValue()
For reference-counted objects, use .deref() in finalize instead of destroy() to release references to other JS objects

Files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/shell/subproc.zig
  • src/sys.zig
  • src/bun.js/api/bun/subprocess.zig
  • src/io/PipeReader.zig
  • src/bun.js/api/bun/subprocess/Readable.zig
  • src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
test/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts,jsx,tsx}: Write tests as JavaScript and TypeScript files using Jest-style APIs (test, describe, expect) and import from bun:test
Use test.each and data-driven tests to reduce boilerplate when testing multiple similar cases

Files:

  • test/js/bun/spawn/spawn-pty.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test with files that end in *.test.{ts,js,jsx,tsx,mjs,cjs}
Do not write flaky tests. Never wait for time to pass in tests; always wait for the condition to be met instead of using an arbitrary amount of time
Never use hardcoded port numbers in tests. Always use port: 0 to get a random port
Prefer concurrent tests over sequential tests using test.concurrent or describe.concurrent when multiple tests spawn processes or write files, unless it's very difficult to make them concurrent
When spawning Bun processes in tests, use bunExe and bunEnv from harness to ensure the same build of Bun is used and debug logging is silenced
Use -e flag for single-file tests when spawning Bun processes
Use tempDir() from harness to create temporary directories with files for multi-file tests instead of creating files manually
Prefer async/await over callbacks in tests
When callbacks must be used and it's just a single callback, use Promise.withResolvers to create a promise that can be resolved or rejected from a callback
Do not set a timeout on tests. Bun already has timeouts
Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests, as ''.repeat is very slow in debug JavaScriptCore builds
Use describe blocks for grouping related tests
Always use await using or using to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc
Always check exit codes and test error scenarios in error tests
Use describe.each() for parameterized tests
Use toMatchSnapshot() for snapshot testing
Use beforeAll(), afterEach(), beforeEach() for setup/teardown in tests
Track resources (servers, clients) in arrays for cleanup in afterEach()

Files:

  • test/js/bun/spawn/spawn-pty.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: For single-file tests in Bun test suite, prefer using -e flag over tempDir
For multi-file tests in Bun test suite, prefer using tempDir and Bun.spawn
Always use port: 0 when spawning servers in tests - do not hardcode ports or use custom random port functions
Use normalizeBunSnapshot to normalize snapshot output in tests instead of manual output comparison
Never write tests that check for no 'panic', 'uncaught exception', or similar strings in test output - that is not a valid test
Use tempDir from harness to create temporary directories in tests - do not use tmpdirSync or fs.mkdtempSync
In tests, call expect(stdout).toBe(...) before expect(exitCode).toBe(0) when spawning processes for more useful error messages on failure
Do not write flaky tests - do not use setTimeout in tests; instead await the condition to be met since you're testing the CONDITION, not TIME PASSING
Verify your test fails with USE_SYSTEM_BUN=1 bun test <file> and passes with bun bd test <file> - tests are not valid if they pass with USE_SYSTEM_BUN=1
Avoid shell commands in tests - do not use find or grep; use Bun's Glob and built-in tools instead
Test files must end in .test.ts or .test.tsx and be created in the appropriate test folder structure

Files:

  • test/js/bun/spawn/spawn-pty.test.ts
🧠 Learnings (30)
📓 Common learnings
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Add tests for new Bun runtime functionality
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • src/bun.js/api/bun/subprocess.zig
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • src/bun.js/api/bun/subprocess.zig
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/process.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:34:55.173Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-11-24T18:34:55.173Z
Learning: Applies to src/**/*.zig : Use `bun.Output.scoped(.${SCOPE}, .hidden)` for creating debug logs in Zig code

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/bun.js/api/bun/subprocess.zig
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/shell/subproc.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • src/bun.js/api/bun/subprocess.zig
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:34:55.173Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-11-24T18:34:55.173Z
Learning: Applies to src/**/*.{cpp,zig} : Enable debug logs for specific scopes using `BUN_DEBUG_$(SCOPE)=1` environment variable

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Include new class bindings in `src/bun.js/bindings/generated_classes_list.zig` to register them with the code generator

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-10-17T20:50:58.644Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/Handlers.zig:154-159
Timestamp: 2025-10-17T20:50:58.644Z
Learning: In Bun socket configuration error messages (src/bun.js/api/bun/socket/Handlers.zig), use the user-facing JavaScript names "data" and "drain" instead of internal field names "onData" and "onWritable", as these are the names users see in the API according to SocketConfig.bindv2.ts.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/bun.js/api/bun/subprocess/Readable.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, call `expect(stdout).toBe(...)` before `expect(exitCode).toBe(0)` when spawning processes for more useful error messages on failure

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • test/js/bun/spawn/spawn-pty.test.ts
  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-18T02:06:31.606Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23796
File: src/bun.js/test/Execution.zig:624-625
Timestamp: 2025-10-18T02:06:31.606Z
Learning: In Zig files using bun.Output.scoped(), the visibility level `.visible` means logs are enabled by default in debug builds unless `BUN_DEBUG_QUIET_LOGS=1` is set; if that environment variable is set, the logs can be re-enabled with `BUN_DEBUG_<scope>=1`. Use `.visible` for logs that should appear by default in debug builds, and `.hidden` for logs that require explicit opt-in via `BUN_DEBUG_<scope>=1`.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • src/bun.js/api/bun/subprocess/Readable.zig
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures and not tests themselves

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : Verify your test fails with `USE_SYSTEM_BUN=1 bun test <file>` and passes with `bun bd test <file>` - tests are not valid if they pass with `USE_SYSTEM_BUN=1`

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.

Applied to files:

  • test/js/bun/spawn/spawn-pty.test.ts
📚 Learning: 2025-10-04T21:17:53.040Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23253
File: test/js/valkey/valkey.failing-subscriber-no-ipc.ts:40-59
Timestamp: 2025-10-04T21:17:53.040Z
Learning: In Bun runtime, the global `console` object is an AsyncIterable that yields lines from stdin. The pattern `for await (const line of console)` is valid and documented in Bun for reading input line-by-line from the child process stdin.

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-18T01:49:31.037Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/SocketConfig.bindv2.ts:58-58
Timestamp: 2025-10-18T01:49:31.037Z
Learning: In Bun's bindgenv2 TypeScript bindings (e.g., src/bun.js/api/bun/socket/SocketConfig.bindv2.ts), the pattern `b.String.loose.nullable.loose` is intentional and not a duplicate. The first `.loose` applies to the String type (loose string conversion), while the second `.loose` applies to the nullable (loose nullable, treating all falsy values as null rather than just null/undefined).

Applied to files:

  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-11T22:55:08.547Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24571
File: src/css/values/url.zig:97-116
Timestamp: 2025-11-11T22:55:08.547Z
Learning: In Zig 0.15, the standard library module `std.io` was renamed to `std.Io` (with capital I). Code using `std.Io.Writer.Allocating` and similar types is correct for Zig 0.15+.

Applied to files:

  • src/io/PipeReader.zig
📚 Learning: 2025-11-24T18:35:39.205Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Applies to **/js_*.zig : Use `bun.JSError!JSValue` for proper error propagation in JavaScript bindings

Applied to files:

  • src/bun.js/api/bun/subprocess/Readable.zig
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/api/bun/subprocess/Readable.zig
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to **/*.zig : Use `bun.JSError!JSValue` return type for Zig methods and constructors to enable proper error handling and exception propagation

Applied to files:

  • src/bun.js/api/bun/subprocess/Readable.zig
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations

Applied to files:

  • src/bun.js/api/bun/subprocess/Readable.zig
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:35:08.612Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:08.612Z
Learning: Applies to test/bake/dev/esm.test.ts : Organize ESM tests in esm.test.ts for tests about various ESM features in development mode

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Export modules using `export default { ... }` syntax; modules are NOT ES modules

Applied to files:

  • packages/bun-types/bun.d.ts
🧬 Code graph analysis (1)
test/js/bun/spawn/spawn-pty.test.ts (2)
test/harness.ts (1)
  • bunExe (102-105)
packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts (1)
  • Response (2793-2806)
🔇 Additional comments (17)
src/bun.js/api/bun/js_bun_spawn_bindings.zig (1)

1006-1006: Log scope .spawn_bindings with .hidden is appropriate

Defining const log = Output.scoped(.spawn_bindings, .hidden); gives this module its own opt‑in debug channel and aligns with the project logging guidelines for new scopes. No issues here.

test/js/bun/spawn/spawn-pty.test.ts (1)

1-167: PTY test coverage is thorough and well-structured

The new tests cover the key PTY behaviors end‑to‑end (isTTY flags across stdio, shared PTY, custom dimensions, color output, concurrency, and spawnSync rejection) using bunExe/bunEnv and -e scripts as recommended. Assertions read stdout before exit codes and use .trim()/.toContain() to stay robust against \r\n translation.

One thing to keep in mind is that the spawnSync tests assert the exact error message "PTY is not supported with spawnSync". If the implementation message changes in the future, these will need updating; otherwise the suite looks solid.

src/bun.js/api/bun/subprocess/Readable.zig (2)

73-81: PTY shared-master handling looks correct.

When multiple stdio streams use PTY, they share the same master FD. The logic correctly handles result == null for the secondary stream (stderr) by ignoring it, since stdout handles the reading. The non-null path correctly creates a PTY-aware pipe reader.


87-90: LGTM!

Naming the error parameter and adding the debug log improves observability for PTY lifecycle debugging.

src/bun.js/api/bun/subprocess/Writable.zig (1)

231-254: PTY stdin handling implementation looks correct.

The implementation correctly mirrors the .pipe path but omits the .socket flag since PTY masters are character devices that use write() rather than send(). The subprocess lifecycle management (weak pointer, ref counting, destructor flags) is properly set up.

docs/runtime/child-process.mdx (1)

118-231: Comprehensive and well-structured PTY documentation.

The documentation covers all essential aspects: basic usage, isTTY verification, multiple streams sharing PTY, custom terminal dimensions, colored output examples, platform support matrix, and limitations. This will be helpful for users adopting the feature.

src/io/PipeReader.zig (2)

86-100: PTY flag and padding look correct.

The is_pty flag addition with 5-bit padding maintains the packed struct(u16) alignment (11 bools + 5 padding = 16 bits). The comment on line 97-98 clearly documents the PTY-specific EIO handling behavior.


275-281: Correct PTY EIO-as-EOF handling.

When the PTY slave side closes (child exits), the master receives EIO. Treating this as EOF rather than an error is the standard PTY behavior. The closeWithoutReporting() followed by done() ensures proper cleanup without propagating a spurious error to the caller.

src/bun.js/api/bun/spawn/stdio.zig (2)

17-26: LGTM!

The PtyOptions struct with sensible defaults (80x24) matches standard terminal dimensions. The documentation comments clearly explain the fields.


361-373: LGTM!

The string "pty" parsing correctly handles:

  1. spawnSync rejection with clear error message
  2. Windows fallback to pipe
  3. Default PTY options for POSIX
src/bun.js/api/bun/process.zig (3)

1007-1013: LGTM!

The PtyConfig struct is well-designed with sensible 80x24 defaults matching standard terminal dimensions.


1314-1344: LGTM!

The PTY creation logic correctly:

  • Creates a single PTY pair shared by all PTY stdio streams
  • Uses the first PTY config's dimensions (reasonable since all share the same terminal)
  • Properly tracks FDs for cleanup (slave for post-spawn close, master for error-only close)
  • Sets non-blocking mode only for async operations

1462-1483: LGTM!

The PTY stdio handling correctly:

  • Uses the slave side for the child process
  • Merges stderr into stdout when both use PTY (single stream)
  • Duplicates the master FD for independent epoll registration per stream
  • Maintains non-blocking consistency with the sync mode
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig (4)

17-18: LGTM!

Good documentation explaining the semantic difference between PTY (character device) and regular pipes (sockets).


38-62: LGTM!

Clean refactoring with factory methods that maintain backward compatibility while adding PTY support.


77-98: LGTM!

Correctly:

  • Sets PTY flag before start() so error handlers can check it during poll registration
  • Avoids marking PTY as socket since it's a character device requiring read() instead of recv()

190-196: LGTM!

Correctly handles the PTY-specific behavior where EIO from the master indicates the slave side closed (child process exited). Treating this as EOF rather than an error is the standard approach for PTY handling.

mmap,
munmap,
open,
openpty,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

PTY scaffolding (Tag.openpty + openpty helper) looks sound

The new syscall tag and openpty wrapper are consistent with existing patterns: Tag.openpty integrates cleanly into the enum, WinSize matches the expected C struct winsize layout, and openpty() correctly treats non‑zero return values as errors and pulls errno via std.posix.errno(rc). I don’t see correctness or portability issues here; higher‑level PTY code can safely build on this helper.

If you want to keep syscall logging quieter in debug builds, you could optionally gate the log("openpty() = ...) behind Environment.allow_assert like other logging in this file, but that’s stylistic, not required.

Also applies to: 4281-4342

🤖 Prompt for AI Agents
In src/sys.zig around line 224 (and also the openpty implementation region at
4281-4342), the debug log for openpty() is unconditionally emitted; to make
syscall logging consistent with other entries, wrap the log call in the same
Environment.allow_assert guard used elsewhere so the message is only printed
when allowed (i.e., check Environment.allow_assert before calling log), leaving
the rest of the openpty implementation and error handling unchanged.

- Fix docs example consistency: use array form consistently
- Add spawnSync warning to TypeScript PTY type definitions
- Fix Windows compile error in js_bun_spawn_bindings.zig
- Fix extra_fds PTY to use dup() for consistency
- Add isNumber() type check for PTY width/height options
- Fix error message consistency in stdio.zig
- Fix switch case overlap in shell/subproc.zig (remove .pipe/.readable_stream that were already handled)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add .pty case to the Windows switch statement in Writable.zig.
On Windows, PTY falls back to pipe behavior, so stdin with PTY
returns ignore (same as other unsupported stdin types).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bun-types/bun.d.ts (1)

5334-5353: "pty" stdio mode not reflected in Subprocess IO types

Spawn.Readable / Spawn.Writable now include "pty", but the helper mappings still only special‑case "pipe":

  • ReadableToIO<"pty"> and ReadableToSyncIO<"pty"> currently resolve to undefined, so subprocess.stdout / stderr are typed as undefined when using "pty", even though the runtime exposes a readable stream/buffer.
  • WritableToIO<"pty"> resolves to undefined, so subprocess.stdin is also incorrectly typed when using "pty".

This makes the new PTY mode effectively untyped from the caller’s perspective and will surprise TypeScript users relying on Subprocess generics.

If "pty" behaves like "pipe" from the parent’s point of view (stream/FileSink interface with TTY semantics in the child), the mapping helpers should treat "pty" the same as "pipe":

-    type ReadableToIO<X extends Readable> = X extends "pipe" | undefined
+    type ReadableToIO<X extends Readable> = X extends "pipe" | "pty" | undefined
       ? ReadableStream<Uint8Array<ArrayBuffer>>
       : X extends BunFile | ArrayBufferView | number
         ? number
         : undefined;

-    type ReadableToSyncIO<X extends Readable> = X extends "pipe" | undefined ? Buffer : undefined;
+    type ReadableToSyncIO<X extends Readable> = X extends "pipe" | "pty" | undefined ? Buffer : undefined;

     type WritableIO = FileSink | number | undefined;

-    type WritableToIO<X extends Writable> = X extends "pipe"
+    type WritableToIO<X extends Writable> = X extends "pipe" | "pty"
       ? FileSink
       : X extends BunFile | ArrayBufferView | Blob | Request | Response | number
         ? number
         : undefined;

Also applies to: 5688-5703

♻️ Duplicate comments (1)
packages/bun-types/bun.d.ts (1)

5411-5425: PTY JSDoc and spawnSync limitation are clearly documented

The new "pty" bullets for stdio, stdin, stdout, and stderr correctly describe the TTY semantics, Windows fallback to "pipe", and the fact that "pty" is not supported with spawnSync. This addresses the earlier documentation gap and keeps behavior expectations clear.

Also applies to: 5434-5434, 5447-5447, 5460-5460

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d4965c and 0245de5.

📒 Files selected for processing (6)
  • docs/runtime/child-process.mdx (4 hunks)
  • packages/bun-types/bun.d.ts (9 hunks)
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig (2 hunks)
  • src/bun.js/api/bun/process.zig (5 hunks)
  • src/bun.js/api/bun/spawn/stdio.zig (5 hunks)
  • src/shell/subproc.zig (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/shell/subproc.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Use bun.Output.scoped(.${SCOPE}, .hidden) for creating debug logs in Zig code

Implement core functionality in Zig, typically in its own directory in src/

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Be careful with memory management in Zig code - use defer for cleanup with allocators

Files:

  • src/shell/subproc.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.zig: Expose generated bindings in Zig structs using pub const js = JSC.Codegen.JS<ClassName> with trait conversion methods: toJS, fromJS, and fromJSDirect
Use consistent parameter name globalObject instead of ctx in Zig constructor and method implementations
Use bun.JSError!JSValue return type for Zig methods and constructors to enable proper error handling and exception propagation
Implement resource cleanup using deinit() method that releases resources, followed by finalize() called by the GC that invokes deinit() and frees the pointer
Use JSC.markBinding(@src()) in finalize methods for debugging purposes before calling deinit()
For methods returning cached properties in Zig, declare external C++ functions using extern fn and callconv(JSC.conv) calling convention
Implement getter functions with naming pattern get<PropertyName> in Zig that accept this and globalObject parameters and return JSC.JSValue
Access JavaScript CallFrame arguments using callFrame.argument(i), check argument count with callFrame.argumentCount(), and get this with callFrame.thisValue()
For reference-counted objects, use .deref() in finalize instead of destroy() to release references to other JS objects

Files:

  • src/shell/subproc.zig
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
**/js_*.zig

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

**/js_*.zig: Implement JSYourFeature struct in a file like js_your_feature.zig to create JavaScript bindings for Zig functionality
Use bun.JSError!JSValue for proper error propagation in JavaScript bindings
Implement proper memory management with reference counting using ref()/deref() in JavaScript bindings
Always implement proper cleanup in deinit() and finalize() methods for JavaScript binding classes

Files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
🧠 Learnings (37)
📓 Common learnings
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Add tests for new Bun runtime functionality
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • src/shell/subproc.zig
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-09-02T19:17:26.376Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 0
File: :0-0
Timestamp: 2025-09-02T19:17:26.376Z
Learning: In Bun's Zig codebase, when handling error unions where the same cleanup operation (like `rawFree`) needs to be performed regardless of success or failure, prefer using boolean folding with `else |err| switch (err)` over duplicating the cleanup call in multiple switch branches. This approach avoids code duplication while maintaining compile-time error checking.

Applied to files:

  • src/shell/subproc.zig
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/process.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`

Applied to files:

  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.

Applied to files:

  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always use `await using` or `using` to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-17T20:50:58.644Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/Handlers.zig:154-159
Timestamp: 2025-10-17T20:50:58.644Z
Learning: In Bun socket configuration error messages (src/bun.js/api/bun/socket/Handlers.zig), use the user-facing JavaScript names "data" and "drain" instead of internal field names "onData" and "onWritable", as these are the names users see in the API according to SocketConfig.bindv2.ts.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-10-04T21:17:53.040Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23253
File: test/js/valkey/valkey.failing-subscriber-no-ipc.ts:40-59
Timestamp: 2025-10-04T21:17:53.040Z
Learning: In Bun runtime, the global `console` object is an AsyncIterable that yields lines from stdin. The pattern `for await (const line of console)` is valid and documented in Bun for reading input line-by-line from the child process stdin.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, call `expect(stdout).toBe(...)` before `expect(exitCode).toBe(0)` when spawning processes for more useful error messages on failure

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-10-18T01:49:31.037Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/SocketConfig.bindv2.ts:58-58
Timestamp: 2025-10-18T01:49:31.037Z
Learning: In Bun's bindgenv2 TypeScript bindings (e.g., src/bun.js/api/bun/socket/SocketConfig.bindv2.ts), the pattern `b.String.loose.nullable.loose` is intentional and not a duplicate. The first `.loose` applies to the String type (loose string conversion), while the second `.loose` applies to the nullable (loose nullable, treating all falsy values as null rather than just null/undefined).

Applied to files:

  • docs/runtime/child-process.mdx
  • packages/bun-types/bun.d.ts
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-11-12T04:11:52.293Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 24622
File: src/deps/uws/us_socket_t.zig:112-113
Timestamp: 2025-11-12T04:11:52.293Z
Learning: In Bun's Zig codebase, when passing u32 values to C FFI functions that expect c_uint parameters, no explicit intCast is needed because c_uint is equivalent to u32 on Bun's target platforms and Zig allows implicit coercion between equivalent types. This pattern is used consistently throughout src/deps/uws/us_socket_t.zig in functions like setTimeout, setLongTimeout, and setKeepalive.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to **/*.zig : Use `bun.JSError!JSValue` return type for Zig methods and constructors to enable proper error handling and exception propagation

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-11-24T18:35:39.205Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Applies to **/js_*.zig : Use `bun.JSError!JSValue` for proper error propagation in JavaScript bindings

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-09-04T02:04:43.094Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22278
File: src/ast/E.zig:980-1003
Timestamp: 2025-09-04T02:04:43.094Z
Learning: In Bun's Zig codebase, `as(i32, u8_or_u16_value)` is sufficient for casting u8/u16 to i32 in comparison operations. `intCast` is not required in this context, and the current casting approach compiles successfully.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-10-18T20:59:45.579Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:458-475
Timestamp: 2025-10-18T20:59:45.579Z
Learning: In src/bun.js/telemetry.zig, the RequestId (u64) to JavaScript number (f64) conversion in jsRequestId() is intentionally allowed to lose precision beyond 2^53-1. This is acceptable because: (1) at 1M requests/sec it takes ~285 years to overflow, (2) the counter resets per-process, and (3) these are observability IDs, not critical distributed IDs. Precision loss is an acceptable trade-off for this use case.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-10-18T20:50:47.750Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:366-373
Timestamp: 2025-10-18T20:50:47.750Z
Learning: In Bun's Zig codebase (src/bun.js/bindings/JSValue.zig), the JSValue enum uses `.null` (not `.js_null`) for JavaScript's null value. Only `js_undefined` has the `js_` prefix to avoid collision with Zig's built-in `undefined` keyword. The correct enum fields are: `js_undefined`, `null`, `true`, `false`, and `zero`.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/bun.js/api/bun/spawn/stdio.zig
📚 Learning: 2025-11-24T18:34:55.173Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-11-24T18:34:55.173Z
Learning: Applies to src/**/*.zig : Use `bun.Output.scoped(.${SCOPE}, .hidden)` for creating debug logs in Zig code

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Include new class bindings in `src/bun.js/bindings/generated_classes_list.zig` to register them with the code generator

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use `process.platform` and `process.arch` for platform detection; these values are inlined and dead-code eliminated at build time

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-10-18T02:06:31.606Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23796
File: src/bun.js/test/Execution.zig:624-625
Timestamp: 2025-10-18T02:06:31.606Z
Learning: In Zig files using bun.Output.scoped(), the visibility level `.visible` means logs are enabled by default in debug builds unless `BUN_DEBUG_QUIET_LOGS=1` is set; if that environment variable is set, the logs can be re-enabled with `BUN_DEBUG_<scope>=1`. Use `.visible` for logs that should appear by default in debug builds, and `.hidden` for logs that require explicit opt-in via `BUN_DEBUG_<scope>=1`.

Applied to files:

  • src/bun.js/api/bun/js_bun_spawn_bindings.zig
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:35:08.612Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:08.612Z
Learning: Applies to test/bake/dev/esm.test.ts : Organize ESM tests in esm.test.ts for tests about various ESM features in development mode

Applied to files:

  • packages/bun-types/bun.d.ts
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Export modules using `export default { ... }` syntax; modules are NOT ES modules

Applied to files:

  • packages/bun-types/bun.d.ts
🔇 Additional comments (5)
src/bun.js/api/bun/js_bun_spawn_bindings.zig (1)

638-646: POSIX‑gated FD logging + dedicated scope look correct

The new log uses POSIX‑only optional FDs and falls back to a simpler message on Windows, which fixes the earlier type mismatch, and the .spawn_bindings .hidden scope keeps this as opt‑in debug output. No functional issues here.

Also applies to: 1010-1010

src/bun.js/api/bun/spawn/stdio.zig (1)

17-26: PTY stdio parsing and platform behavior look solid

  • Adding pty: PtyOptions { width, height } to Stdio with POSIX → .pty and Windows → .buffer (pipe) matches the documented semantics.
  • The "pty" string and { type: "pty", width?, height? } object forms:
    • Correctly forbid use in spawnSync.
    • Validate width/height types with isNumber() before toInt32(), and enforce 0 < value ≤ 65535 before casting to u16.
    • Apply the same Windows fallback to "pipe".

The unified invalid‑value error message now lists "pty" and all other supported forms, which makes the API surface clearer. No issues from a runtime or typing perspective.

Also applies to: 205-206, 258-259, 361-373, 461-510

packages/bun-types/bun.d.ts (3)

1743-1746: Doc reflow around format union looks fine

Aligning the "esm" case with its JSDoc is stylistic only and keeps the type readable; no issues here.


3319-3322: Helpful JSDoc addition for "ansi" output format

Documenting that "ansi" returns a true‑color SGR escape (with an explicit example) is accurate and makes the overload clearer; no further changes needed.


5660-5666: Interfaces for SpawnSyncOptions / SpawnOptions are reasonable

Switching to interface SpawnSyncOptions and interface SpawnOptions that extend BaseOptions keeps the public surface unchanged while letting you hang spawn‑only docs like lazy off the async options type; the signatures remain structurally compatible with existing code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0245de5 and bc23962.

📒 Files selected for processing (1)
  • src/bun.js/api/bun/subprocess/Writable.zig (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/bun.js/api/bun/subprocess/Writable.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Use bun.Output.scoped(.${SCOPE}, .hidden) for creating debug logs in Zig code

Implement core functionality in Zig, typically in its own directory in src/

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Be careful with memory management in Zig code - use defer for cleanup with allocators

Files:

  • src/bun.js/api/bun/subprocess/Writable.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.zig: Expose generated bindings in Zig structs using pub const js = JSC.Codegen.JS<ClassName> with trait conversion methods: toJS, fromJS, and fromJSDirect
Use consistent parameter name globalObject instead of ctx in Zig constructor and method implementations
Use bun.JSError!JSValue return type for Zig methods and constructors to enable proper error handling and exception propagation
Implement resource cleanup using deinit() method that releases resources, followed by finalize() called by the GC that invokes deinit() and frees the pointer
Use JSC.markBinding(@src()) in finalize methods for debugging purposes before calling deinit()
For methods returning cached properties in Zig, declare external C++ functions using extern fn and callconv(JSC.conv) calling convention
Implement getter functions with naming pattern get<PropertyName> in Zig that accept this and globalObject parameters and return JSC.JSValue
Access JavaScript CallFrame arguments using callFrame.argument(i), check argument count with callFrame.argumentCount(), and get this with callFrame.thisValue()
For reference-counted objects, use .deref() in finalize instead of destroy() to release references to other JS objects

Files:

  • src/bun.js/api/bun/subprocess/Writable.zig
🧠 Learnings (3)
📓 Common learnings
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • src/bun.js/api/bun/subprocess/Writable.zig
📚 Learning: 2025-10-17T20:50:58.644Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/Handlers.zig:154-159
Timestamp: 2025-10-17T20:50:58.644Z
Learning: In Bun socket configuration error messages (src/bun.js/api/bun/socket/Handlers.zig), use the user-facing JavaScript names "data" and "drain" instead of internal field names "onData" and "onWritable", as these are the names users see in the API according to SocketConfig.bindv2.ts.

Applied to files:

  • src/bun.js/api/bun/subprocess/Writable.zig
🔇 Additional comments (1)
src/bun.js/api/bun/subprocess/Writable.zig (1)

235-258: LGTM - Correct PTY implementation for POSIX.

The PTY stdin handling correctly:

  • Creates a FileSink with the PTY master fd
  • Starts the writer without the .socket flag (PTY masters are character devices, not sockets)
  • Sets up subprocess references consistently with the regular pipe case
  • Uses write() syscalls rather than send()

The explicit comment about not setting the socket flag is excellent defensive documentation that prevents future bugs.

- Fix PTY master FD leak: close original master FD after spawn succeeds
  since parent uses dup()'d copies for stdio/extra_fds
- Fix PosixSpawnResult.close: correct type signature and add PTY handling
- Harmonize shell PTY behavior: change POSIX stdin panic to return ignore
  (consistent with Windows behavior)
- Update docs: add "Not supported with spawnSync" to PTY table entries
- Update docs: expand TS type comments with platform/spawnSync notes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude Bot and others added 2 commits December 3, 2025 11:35
Update comment to clearly state PTY stdin is not supported on Windows
rather than misleadingly mentioning "falls back to pipe".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/bun.js/api/bun/process.zig (1)

1577-1582: Original PTY master closed but spawned.pty_master still references it.

After closing the original PTY master FD (line 1581), spawned.pty_master still holds the closed FD value. While PosixSpawnResult.close() will try to close it again (line 1122-1124), this could lead to confusion or double-close if close() is called. Consider setting spawned.pty_master = null after closing.

Apply this diff for clarity and safety:

             // Parent uses dup()'d copies of the PTY master for stdio/extra_fds;
             // the original master FD is no longer needed and should be closed
             // to avoid leaking one FD per PTY spawn.
             if (pty_master) |fd| {
                 fd.close();
+                spawned.pty_master = null;
             }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bc23962 and 064b1d3.

📒 Files selected for processing (4)
  • docs/runtime/child-process.mdx (4 hunks)
  • src/bun.js/api/bun/process.zig (6 hunks)
  • src/bun.js/api/bun/subprocess/Writable.zig (2 hunks)
  • src/shell/subproc.zig (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/bun.js/api/bun/process.zig
  • src/shell/subproc.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Use bun.Output.scoped(.${SCOPE}, .hidden) for creating debug logs in Zig code

Implement core functionality in Zig, typically in its own directory in src/

src/**/*.zig: Private fields in Zig are fully supported using the # prefix: struct { #foo: u32 };
Use decl literals in Zig for declaration initialization: const decl: Decl = .{ .binding = 0, .value = 0 };
Prefer @import at the bottom of the file (auto formatter will move them automatically)

Be careful with memory management in Zig code - use defer for cleanup with allocators

Files:

  • src/bun.js/api/bun/process.zig
  • src/shell/subproc.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.zig: Expose generated bindings in Zig structs using pub const js = JSC.Codegen.JS<ClassName> with trait conversion methods: toJS, fromJS, and fromJSDirect
Use consistent parameter name globalObject instead of ctx in Zig constructor and method implementations
Use bun.JSError!JSValue return type for Zig methods and constructors to enable proper error handling and exception propagation
Implement resource cleanup using deinit() method that releases resources, followed by finalize() called by the GC that invokes deinit() and frees the pointer
Use JSC.markBinding(@src()) in finalize methods for debugging purposes before calling deinit()
For methods returning cached properties in Zig, declare external C++ functions using extern fn and callconv(JSC.conv) calling convention
Implement getter functions with naming pattern get<PropertyName> in Zig that accept this and globalObject parameters and return JSC.JSValue
Access JavaScript CallFrame arguments using callFrame.argument(i), check argument count with callFrame.argumentCount(), and get this with callFrame.thisValue()
For reference-counted objects, use .deref() in finalize instead of destroy() to release references to other JS objects

Files:

  • src/bun.js/api/bun/process.zig
  • src/shell/subproc.zig
  • src/bun.js/api/bun/subprocess/Writable.zig
🧠 Learnings (16)
📓 Common learnings
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • src/bun.js/api/bun/process.zig
  • src/shell/subproc.zig
  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/subprocess/Writable.zig
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • src/bun.js/api/bun/process.zig
  • docs/runtime/child-process.mdx
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr

Applied to files:

  • src/bun.js/api/bun/process.zig
  • docs/runtime/child-process.mdx
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • src/bun.js/api/bun/process.zig
  • docs/runtime/child-process.mdx
📚 Learning: 2025-09-02T19:17:26.376Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 0
File: :0-0
Timestamp: 2025-09-02T19:17:26.376Z
Learning: In Bun's Zig codebase, when handling error unions where the same cleanup operation (like `rawFree`) needs to be performed regardless of success or failure, prefer using boolean folding with `else |err| switch (err)` over duplicating the cleanup call in multiple switch branches. This approach avoids code duplication while maintaining compile-time error checking.

Applied to files:

  • src/shell/subproc.zig
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `-e` flag for single-file tests when spawning Bun processes

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : For multi-file tests in Bun test suite, prefer using `tempDir` and `Bun.spawn`

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/subprocess/Writable.zig
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Always use `await using` or `using` to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-17T20:50:58.644Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/api/bun/socket/Handlers.zig:154-159
Timestamp: 2025-10-17T20:50:58.644Z
Learning: In Bun socket configuration error messages (src/bun.js/api/bun/socket/Handlers.zig), use the user-facing JavaScript names "data" and "drain" instead of internal field names "onData" and "onWritable", as these are the names users see in the API according to SocketConfig.bindv2.ts.

Applied to files:

  • docs/runtime/child-process.mdx
  • src/bun.js/api/bun/subprocess/Writable.zig
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use `process.platform` and `process.arch` for platform detection; these values are inlined and dead-code eliminated at build time

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-12-02T05:59:51.485Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-02T05:59:51.485Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, call `expect(stdout).toBe(...)` before `expect(exitCode).toBe(0)` when spawning processes for more useful error messages on failure

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-04T21:17:53.040Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23253
File: test/js/valkey/valkey.failing-subscriber-no-ipc.ts:40-59
Timestamp: 2025-10-04T21:17:53.040Z
Learning: In Bun runtime, the global `console` object is an AsyncIterable that yields lines from stdin. The pattern `for await (const line of console)` is valid and documented in Bun for reading input line-by-line from the child process stdin.

Applied to files:

  • docs/runtime/child-process.mdx
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/bun.js/api/bun/subprocess/Writable.zig
🔇 Additional comments (12)
src/shell/subproc.zig (3)

181-185: LGTM - PTY handling for shell stdin on Windows.

The shell intentionally ignores PTY for stdin, which is documented in the comment. This is consistent with the PR's approach that the shell layer doesn't consume PTYs directly.


223-230: LGTM - Graceful fallback for PTY and readable_stream.

Replacing the previous panic with a graceful ignore fallback is the right approach. The comments clearly document that the shell never uses these paths directly.


380-381: LGTM - Consistent PTY handling in Readable.init.

Both Windows and POSIX paths consistently ignore PTY for readable streams, with clear comments explaining the shell doesn't use PTY directly.

Also applies to: 402-404

src/bun.js/api/bun/subprocess/Writable.zig (2)

156-159: LGTM - Windows PTY handling with corrected comment.

The comment now correctly states that PTY stdin is not supported on Windows (returns ignore), which aligns with the implementation.


235-258: LGTM - PTY handling correctly mirrors pipe path without socket flag.

The PTY implementation correctly:

  1. Creates a FileSink with the PTY master fd
  2. Starts the writer with proper error handling
  3. Omits the .socket flag (correct for PTY character devices which use write() not send())
  4. Sets all lifecycle flags identically to the pipe path

The comment at lines 247-248 clearly documents why the socket flag is intentionally not set.

docs/runtime/child-process.mdx (3)

42-55: LGTM - Input stream table updated with PTY option.

The table clearly documents the PTY option with appropriate caveats about Windows fallback and spawnSync limitations.


118-223: LGTM - Comprehensive PTY documentation.

The new PTY section is well-structured and covers:

  • Basic usage with clear examples
  • Multiple PTY streams with shared terminal explanation
  • Custom terminal dimensions with object syntax
  • Practical use cases (colored output from git, grep, etc.)
  • Platform support table with clear Windows fallback behavior
  • Limitations (spawnSync, line endings, no dynamic resize)

The examples use consistent array-form spawn syntax throughout.


521-537: LGTM - Reference types with PTY documentation.

The type definitions correctly include "pty" with inline comments explaining platform support (macOS/Linux only), Windows fallback behavior, and spawnSync limitations.

src/bun.js/api/bun/process.zig (4)

1007-1013: LGTM - PtyConfig struct with sensible defaults.

The PtyConfig struct with default dimensions of 80 columns × 24 rows matches standard terminal defaults. The width/height are appropriately typed as u16.


1114-1130: LGTM - PosixSpawnResult PTY master handling.

The pty_master field is properly documented, and the close() method correctly:

  1. Closes the PTY master fd if present
  2. Sets it to null after closing
  3. Then closes extra_pipes

The signature is now correctly *PosixSpawnResult (previously flagged in past reviews).


1317-1347: LGTM - PTY pair creation with proper resource management.

The implementation correctly:

  1. Creates a single PTY pair shared across all PTY stdios
  2. Applies window size from the first PTY config encountered
  3. Adds slave to to_close_at_end (closed after spawn regardless of success)
  4. Adds master to to_close_on_error (closed only on error path)
  5. Sets master to non-blocking only for async operations (correct for spawnSync compatibility)

Note: If multiple stdios have different PTY dimensions, the first one wins. This is a reasonable design choice given PTYs are inherently shared.


1533-1545: LGTM - extra_fds PTY handling with proper error cleanup.

The extra_fds path correctly adds duped master FDs to to_close_on_error (line 1542), ensuring cleanup on spawn failure.

Note: If no primary stdio used PTY (pty_slave is null), this silently does nothing. This is acceptable since PTY is primarily intended for stdin/stdout/stderr rather than extra file descriptors.

Add duped master FDs to to_close_on_error list so they are properly
cleaned up if spawn fails after duping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants