Skip to content

bun create: print the git timing line after postinstall, not from the worker thread - #36954

Closed
robobun wants to merge 4 commits into
mainfrom
farm/012eded7/create-git-timing
Closed

robobun wants to merge 4 commits into
mainfrom
farm/012eded7/create-git-timing

Conversation

@robobun

@robobun robobun commented Aug 5, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #36953

Repro

A template with dependencies and a bun-create.postinstall task that streams output. bun create runs git init/add/commit on a worker thread concurrently with install + postinstall, and the worker printed its timing line the moment git finished:

$ bun scripts/install.ts
postinstall: step 1
postinstall: step 2

[2.15s] git          <- interleaved mid-postinstall
postinstall: step 3

Cause

GitHandler::run printed [Xs] git directly from the git worker thread, racing with the postinstall child process writing to the same inherited stderr on the main thread.

Fix

GitHandler::run now records the elapsed time in an atomic, and the line is printed from the main thread after GitHandler::wait() returns, i.e. after postinstall has completed. The synchronous path (--no-install or a dependency-less template) still prints inline as before.

While reproducing, a second bug in the same code path surfaced: on POSIX, a bun-create.postinstall task starting with bun (exactly the reporter's template, "postinstall": "bun scripts/install.ts") silently never ran. exec_task strips the <exe> run prefix, leaving the bare string bun as argv[0], and posix spawn does no PATH lookup, so the spawn failed with ENOENT and the error was discarded. The task now spawns with the absolute self path (display still shows bun), and spawn failures are reported instead of swallowed. This was present before the Rust port (verified on bun 1.3.14); Windows was unaffected because uv_spawn resolves argv[0] against PATH.

Verification

Two tests in test/cli/install/bun-create.test.ts:

  • a stub git whose commit is sequenced against the postinstall task so the unfixed binary reliably prints [Xs] git between postinstall lines; asserts the timing line comes after the last postinstall line
  • a template with "postinstall": "bun scripts/marker.ts"; asserts the task actually ran

Both fail on the unfixed binary and pass with the fix; the full file (23 tests) passes.


[review] gate passed · iteration 1 · 2 files touched

fails on main (without fix)
ASAN without fix: 4 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-create.test.ts
bun test v1.4.0 (3215e1a4b)

test/cli/install/bun-create.test.ts:
(pass) should not crash > ["create"] [116.93ms]
(pass) should not crash > ["create",""] [107.76ms]
(pass) should not crash > ["create","--"] [103.88ms]
(pass) should not crash > ["create","--",""] [109.71ms]
(pass) should not crash > ["create","--help"] [101.85ms]
(pass) should create selected template with @ prefix [490.35ms]
(pass) should create selected template with @ prefix implicit `/create` [484.07ms]
(pass) should create selected template with @ prefix implicit `/create` with version [370.66ms]
(pass) handles a close-delimited GitHub tarball body split across packets [808.93ms]
(pass) keeps tarball entry paths within the destination when checking for conflicting files [286.59ms]
(pass) reports an error and exits when the template's package.json entry body is truncated [262.46ms]
(pass) does not busy-wait on the futex while git runs [1669.93ms]
Copying files... 

[15.00ms] git

[103.00ms] bun create /tmp/cr8-12GwCjp5/bun-create/
... (truncated)

release without fix: 9 FAILED
bun test v1.3.14 (0d9b296a)

test/cli/install/bun-create.test.ts:
(pass) should not crash > ["create"] [2.56ms]
(pass) should not crash > ["create",""] [1.88ms]
(pass) should not crash > ["create","--"] [1.41ms]
(pass) should not crash > ["create","--",""] [1.46ms]
(pass) should not crash > ["create","--help"] [1.82ms]
(pass) should create selected template with @ prefix [59.86ms]
(pass) should create selected template with @ prefix implicit `/create` [45.60ms]
(pass) should create selected template with @ prefix implicit `/create` with version [41.00ms]
(pass) handles a close-delimited GitHub tarball body split across packets [27.30ms]
230 |     },
231 |   });
232 | 
233 |   const [out, err, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
234 | 
235 |   expect(err).not.toContain("could conflict");
                        ^
error: expect(received).not.toContain(expected)

Expected to not contain: "could conflict"
Received: "[github] GET owner/conflict-check-template... \nDecompressing owner/conflict-check-template... \nExtracting owner/conflict-check-template... \n\nerror: The directory dest/ contains files that could conflict:\
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-create.test.ts
bun test v1.4.0 (3215e1a4b)

test/cli/install/bun-create.test.ts:
(pass) should not crash > ["create"] [133.62ms]
(pass) should not crash > ["create",""] [113.43ms]
(pass) should not crash > ["create","--"] [111.50ms]
(pass) should not crash > ["create","--",""] [104.17ms]
(pass) should not crash > ["create","--help"] [107.76ms]
(pass) should create selected template with @ prefix [395.21ms]
(pass) should create selected template with @ prefix implicit `/create` [380.18ms]
(pass) should create selected template with @ prefix implicit `/create` with version [393.65ms]
(pass) handles a close-delimited GitHub tarball body split across packets [827.30ms]
(pass) keeps tarball entry paths within the destination when checking for conflicting files [281.76ms]
(pass) reports an error and exits when the template's package.json entry body is truncated [261.33ms]
(pass) does not busy-wait on the futex while git runs [1678.07ms]
Copying files... 

[17.00ms] git

[106.00ms] bun create /tmp/cr8-1235Qa3X/bun-create/
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     3215e1a4bd
  features     baseline

22 deps, 106 codegen, 1175 objects in 609ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1237] install /workspace/bun
bun install v1.3.14 (0d9b296a)

Checked 124 installs across 170 packages (no changes) [8.00ms]
[2/1237] install /workspace/bun/packages/bun-error
bun install v1.3.14 (0d9b296a)

Checked 1 install across 2 packages (no changes) [3.00ms]
[3/1237] install /workspace/bun/src/node-fallbacks
bun install v1.3.14 (0d9b296a)

Checked 129 installs across 147 packages (no changes) [8.00ms]
[4/1237] gen ErrorCode+*.h
[5/1237] fetch picohttpparser
[picohttpparser] up to date
[6/1237] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[7/1237] fetch tinycc
[tinycc] up to date
[8/1236] gen .bind.ts → GeneratedBindings.cpp
[9/1236] gen bindgenv2
[10/1236] fetch zlib
[zlib] up to date
[11/1236] subst deps/libjpeg-turbo/jconfig.h
[12/1236] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun
... (truncated)
diff hotspot
src/runtime/cli/create_command.rs   |  64 ++++++++++++----
 test/cli/install/bun-create.test.ts | 147 ++++++++++++++++++++++++++++++++++++
 2 files changed, 198 insertions(+), 13 deletions(-)

gate history · 2 passed · 0 rejected · iteration 1

evidence per changed file
file                                 reads  edits  tests
src/runtime/cli/create_command.rs        6     13      0
test/cli/install/bun-create.test.ts      2      2      0

root cause · written by the author bot

The git initialization runs on a background thread concurrently with dependency installation, and that thread printed its timing line directly to the terminal as soon as it finished, so the log raced with a postinstall task writing output on the main thread and landed mid-stream. The fix stops the worker thread from printing, instead recording the elapsed git time and emitting the timing line from the main thread only after the git work has completed and the postinstall output is done. As a related hardening, bun-prefixed postinstall commands now spawn with the resolved absolute Bun executa…

The concurrent git worker thread printed its "[Xs] git" timing line the
moment git finished, racing with postinstall output streaming on the
main thread and landing mid-stream. Record the elapsed time instead and
print the line from the main thread after GitHandler::wait(), once
postinstall has completed. The synchronous path (--no-install or no
dependencies) still prints inline.

Also fix bun-create postinstall tasks starting with "bun " on POSIX:
stripping the "<exe> run" prefix left the bare string "bun" as argv[0],
which posix spawn does not resolve against PATH, so the task silently
never ran (ENOENT was swallowed). Spawn with the absolute self path and
report spawn failures instead of discarding them.

Fixes #36953
Comment thread src/runtime/cli/create_command.rs Outdated
Comment thread src/runtime/cli/create_command.rs Outdated
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

bun create now executes bun-prefixed postinstall commands with the absolute Bun executable path and reports task errors. Git timing is stored and printed after successful completion, preventing interleaving with postinstall output.

bun create runtime behavior

Layer / File(s) Summary
Postinstall task execution
src/runtime/cli/create_command.rs, test/cli/install/bun-create.test.ts
Bun-prefixed tasks spawn with the resolved executable path and preserve their arguments. Spawn and execution errors are reported. Integration coverage validates successful execution.
Git execution and deferred timing
src/runtime/cli/create_command.rs, test/cli/install/bun-create.test.ts
Git execution records elapsed time and prints it after successful completion. Synchronous and background paths use centralized timing output. Integration coverage verifies timing appears after postinstall output.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #36953 by fixing Git timing output ordering and POSIX bun-prefixed postinstall execution.
Out of Scope Changes check ✅ Passed All code and test changes support the linked issue objectives, including the related POSIX postinstall execution fix.
Title check ✅ Passed The title clearly identifies the primary change: moving Git timing output after postinstall output.
Description check ✅ Passed The description explains the problem, cause, fix, related POSIX issue, and verification results with relevant test details.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/cli/create_command.rs`:
- Around line 149-157: Update exec_task to return crate::Result<()> and convert
both spawn and task-execution failures into returned errors that include the
task text, rather than only logging them. Propagate exec_task errors from both
preinstall and postinstall task loops through CreateCommand::exec so bun create
exits nonzero on failure, and add a regression test covering a nonzero
postinstall task.
- Around line 1117-1125: Invert the assignment to create_options.skip_git in the
Git setup block so it reflects whether Git creation failed rather than
succeeded. Keep the existing created result and timing behavior unchanged,
ensuring successful Git setup reports its status and failed setup retains the
skip behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5233ec99-6ee3-47bf-9fcb-3a60dc81f8ef

📥 Commits

Reviewing files that changed from the base of the PR and between b58cd46 and 12accf5.

📒 Files selected for processing (2)
  • src/runtime/cli/create_command.rs
  • test/cli/install/bun-create.test.ts

Comment thread src/runtime/cli/create_command.rs
Comment thread src/runtime/cli/create_command.rs Outdated
The no-dependencies path set skip_git to the git success instead of its
negation, suppressing the final message on success and printing it when
git was missing. Also tighten two comments.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/runtime/cli/create_command.rs (1)

91-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Parse the task before matching bun.

The current split recognizes only literal spaces and preserves empty fields. Therefore, bun\t scripts/install.ts bypasses the bun path, and repeated spaces can add empty argv entries. Parse the task into arguments, match the first argument, and add tab and repeated-whitespace cases to the postinstall test matrix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/cli/create_command.rs` around lines 91 - 99, Update the task
parsing in the npm client handling around the bun detection and argv
construction: tokenize task using whitespace-aware parsing that ignores empty
fields, then inspect the first parsed argument rather than checking the raw task
prefix. Preserve the existing bun execution behavior and add postinstall
coverage for tab-separated and repeated-whitespace bun commands.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/runtime/cli/create_command.rs`:
- Around line 91-99: Update the task parsing in the npm client handling around
the bun detection and argv construction: tokenize task using whitespace-aware
parsing that ignores empty fields, then inspect the first parsed argument rather
than checking the raw task prefix. Preserve the existing bun execution behavior
and add postinstall coverage for tab-separated and repeated-whitespace bun
commands.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d787d7bf-c5cd-48c9-ba57-1106c31b1bff

📥 Commits

Reviewing files that changed from the base of the PR and between 12accf5 and 533921f.

📒 Files selected for processing (2)
  • src/runtime/cli/create_command.rs
  • test/cli/install/bun-create.test.ts

Comment thread src/runtime/cli/create_command.rs
Comment thread src/runtime/cli/create_command.rs
@robobun

robobun commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Re the outside-diff note about tokenizing the task before matching bun: the split-on-single-space parsing (and the bun prefix check) is long-standing behavior carried over from the original implementation, and changing how every existing bun-create task string parses is out of scope for this fix. A bun\t-prefixed task falls through to the <exe> run path, which still executes it.

Comment thread src/runtime/cli/create_command.rs
Comment thread test/cli/install/bun-create.test.ts
The argv[0] substitution was gated on the npm client being present, but a
template with a bun-create postinstall and no dependencies reaches
exec_task without one, leaving the bare string "bun" as argv[0] and the
same posix ENOENT. Gate on the task prefix alone and resolve the self
path directly.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — both earlier findings are addressed in 3215e1a: the bun -prefixed argv[0] substitution now gates on the task prefix alone via self_exe_path() (with a dependency-less-template regression test), and the fixture's sleep 0.5 carries the required comment.

Extended reasoning...

Overview

This PR touches src/runtime/cli/create_command.rs (~50 net lines) and adds four tests to test/cli/install/bun-create.test.ts. It fixes three related bun create defects: (1) the [Xs] git timing line was printed from the git worker thread and interleaved with postinstall output — the elapsed time is now stored in a static AtomicU64 and printed from the main thread after GitHandler::wait() (or inline on the synchronous path); (2) bun -prefixed bun-create postinstall tasks silently ENOENT'd on POSIX because posix spawn does no PATH lookup — argv[0] is now the absolute self path while the printed line keeps bun, and spawn errors are reported instead of discarded; (3) the synchronous git path assigned skip_git to the success value instead of its negation, suppressing the "A local git repository was created" message.

Prior review follow-up

I previously flagged that the argv[0] fix was gated inside if let Some(ref client) = npm_client, leaving the dependency-less-template path (which reaches exec_task with npm_client = None) unfixed. Commit 3215e1a moves the gate to strings::starts_with(task, b"bun ") alone, resolves the path via bun_core::self_exe_path() (a &'static ZStr), and adds a test with no dependencies field that asserts the marker file was written. I also asked for a comment on the bare sleep 0.5 in the git-timing fixture; that comment is present. CodeRabbit's skip_git inversion note and the comment-cop long-comment nits were addressed in 2d2ddbb; robobun's decline of the "propagate task exit code" suggestion is a reasonable scope call (pre-existing policy).

Security risks

None. This is CLI scaffolding output ordering plus argv[0] resolution to the current process's own executable path. No user-controlled data reaches a new sink; the task string parsing (single-space split, bun prefix match) is unchanged pre-existing behavior.

Level of scrutiny

Low-to-moderate. CLI-only, no JSC/GC interaction, no allocator or lifetime changes. The one cross-thread piece — a Release store of elapsed nanoseconds paired with an Acquire load after join() — is straightforward and mirrors the existing SUCCESS atomic. print_start_end(0, elapsed as i128) computes the same delta the original inline call did.

Other factors

Four new tests cover the interleave (POSIX-only via a shell-script git stub with a two-file handshake), both bun -prefixed postinstall variants (with and without dependencies), and the skip_git message. The PR body's evidence shows the file failing on both the debug-ASAN main and release 1.3.14 and passing with the fix. The last test relies on system git rather than a stub — consistent with other tests in this file, and bunEnv spreads process.env so PATH is inherited; the finder/verifier pass ruled this out as a hermeticity concern.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Re-verified against current main (7d276b9): the branch merges cleanly, a debug build of the merge passes test/cli/install/bun-create.test.ts (25 tests), and the four tests added here still fail on bun 1.4.0 without the fix (the timing line lands before PI_END, the two bun -prefixed postinstall tasks never run, and the git message is missing on the dependency-less path).

#38889 was a later PR for the same issue and has been closed in favor of this one.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Heads up on the exec_task half of this: #38955 resolves any bare task command against PATH (which_for_spawn passed as the spawn's argv0), which also covers the bun prefix case here by execing the running bun, and reports tasks that fail to start. The git timing change in this PR is independent. Whichever of the two lands second will have a small conflict in exec_task; if #38955 goes first, this PR can drop its exec_task hunk and the two bun -prefixed postinstall tests are covered there too.

@robobun

robobun commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of two PRs. #41342 fixes the git timing line: git now completes before the tasks start. It also fixes the inverted skip_git for templates without dependencies. #38955 fixes tasks that start with bun . It also resolves other bare commands against PATH.

@robobun robobun closed this Sep 4, 2026
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.

bun create: git commit timing log interleaves with postinstall output

1 participant