Skip to content

fix(serve): allow server config with stop method to auto-start#26748

Open
robobun wants to merge 1 commit intomainfrom
claude/fix-26747-server-stop-method
Open

fix(serve): allow server config with stop method to auto-start#26748
robobun wants to merge 1 commit intomainfrom
claude/fix-26747-server-stop-method

Conversation

@robobun
Copy link
Collaborator

@robobun robobun commented Feb 5, 2026

Summary

  • Fixes the regression where server config objects with a stop method would not auto-start
  • Changed detection from typeof def.stop !== 'function' to typeof def.reload !== 'function'

The previous fix for #26142 used the presence of a stop method to detect Server instances returned by Bun.serve(). However, user config objects can legitimately have a stop method (e.g., Elysia apps), causing them to not auto-start.

Server instances have a reload method that config objects typically don't have, making it a better detection heuristic.

Test plan

  • Added regression test test/regression/issue/26747.test.ts
  • Verified existing test test/regression/issue/26142.test.ts still passes
  • Tested reproduction case from issue manually

Fixes #26747

🤖 Generated with Claude Code

@github-actions github-actions bot added the claude label Feb 5, 2026
@robobun
Copy link
Collaborator Author

robobun commented Feb 5, 2026

Updated 11:53 PM PT - Feb 4th, 2026

❌ Your commit 95c759a5 has 1 failures in Build #36579 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 26748

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

bun-26748 --bun

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Walkthrough

Changed server-config detection to check for the presence of reload (instead of stop) when identifying server config objects. Added regression tests validating auto-start behavior for configs with only stop and for configs with both stop and reload.

Changes

Cohort / File(s) Summary
Server Config Validation
src/bundler/entry_points.zig
Updated isServerConfig logic to use the presence/absence of reload rather than stop when determining server config status; this alters which exported objects trigger server auto-start/reload behavior. Review codepaths that call this predicate during startup and hot-reload.
Regression Tests
test/regression/issue/26747.test.ts
Added two tests: (1) a default-exported config with stop only should auto-start the server and produce a "Started" stdout; (2) a config with both stop and reload should not auto-start. Tests spawn Bun processes, capture stdout/stderr, and assert exit codes and output. Ensure test process IO and temp files are handled.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: allowing server configs with a stop method to auto-start, which aligns with the code changes and issue fix.
Description check ✅ Passed The description covers the summary, rationale, test plan, and linked issue reference. However, it lacks explicit sections matching the template structure (What does this PR do? How did you verify?).
Linked Issues check ✅ Passed Code changes correctly address issue #26747 by switching detection from stop to reload method, matching the root cause analysis and enabling auto-start for configs with stop methods.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the auto-start regression: code fix in entry_points.zig and regression test in 26747.test.ts with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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: 2

🤖 Fix all issues with AI agents
In `@test/regression/issue/26747.test.ts`:
- Around line 35-36: The test currently uses a hard setTimeout (const timeout =
setTimeout(() => proc.kill(), 3000)) to terminate the server; replace this with
an await-based approach that waits for the expected startup condition/output and
then closes the process (e.g., await the success log or use a Promise.race with
a deadline promise) or, if the timeout is truly required, add an explicit
comment explaining why a fixed timeout and proc.kill() are necessary; update
references to timeout and proc.kill() accordingly and ensure any created timer
is cleared when the process exits.
- Around line 10-55: The first test's Bun.spawn instance (proc) is cleaned up
manually with try/finally and proc.kill(); change it to use the same RAII
pattern as the other test by declaring the spawned process with "await using
proc = Bun.spawn(...)" so Bun will auto-cleanup; move the timeout setup/clear
inside that scope (or use a finally to clear it) and remove the manual
proc.kill() in the finally block so the test uses automatic disposal for the
proc created by Bun.spawn.

Comment on lines 35 to 36
// Set a timeout to kill the server after checking output
const timeout = setTimeout(() => proc.kill(), 3000);
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

Avoid setTimeout in tests; prefer awaiting a condition.

As per coding guidelines, tests should not use setTimeout. Instead, consider using a pattern that awaits the expected condition or uses a deadline-based approach.

However, given that this test needs to verify server startup and then terminate a long-running process, the current approach may be acceptable. Consider documenting why the timeout is necessary here.

Alternative approach using race pattern
-  // Set a timeout to kill the server after checking output
-  const timeout = setTimeout(() => proc.kill(), 3000);
-
   try {
     // Wait for first bit of stdout to verify server started
     const reader = proc.stdout.getReader();
-    const { value } = await reader.read();
+    const { value } = await Promise.race([
+      reader.read(),
+      new Promise((_, reject) => 
+        Bun.sleep(3000).then(() => reject(new Error("Timeout waiting for server start")))
+      ),
+    ]);
     reader.releaseLock();
🤖 Prompt for AI Agents
In `@test/regression/issue/26747.test.ts` around lines 35 - 36, The test currently
uses a hard setTimeout (const timeout = setTimeout(() => proc.kill(), 3000)) to
terminate the server; replace this with an await-based approach that waits for
the expected startup condition/output and then closes the process (e.g., await
the success log or use a Promise.race with a deadline promise) or, if the
timeout is truly required, add an explicit comment explaining why a fixed
timeout and proc.kill() are necessary; update references to timeout and
proc.kill() accordingly and ensure any created timer is cleared when the process
exits.

});

// Set a timeout to kill the server after checking output
const timeout = setTimeout(() => proc.kill(), 3000);
Copy link
Contributor

Choose a reason for hiding this comment

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

CLAUDE.md violation: Tests should not use setTimeout.

From CLAUDE.md:

CRITICAL: Do not write flaky tests. Do not use setTimeout in tests. Instead, await the condition to be met.

Instead of using a timeout to kill the server, consider awaiting a specific condition (e.g., waiting for the first stdout chunk with a deadline pattern using Promise.race).

See also:

bun/test/CLAUDE.md

Lines 118 to 120 in 2b9f0c0

### No timeouts
**CRITICAL**: Do not set a timeout on tests. Bun already has timeouts.

`,
});

const proc = Bun.spawn({
Copy link
Contributor

Choose a reason for hiding this comment

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

CLAUDE.md violation: Prefer await using for spawned processes to ensure proper cleanup.

From test/CLAUDE.md:

Generally, await using or using is a good idea to ensure proper resource cleanup. This works in most Bun APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc.

Note: The second test on line 74 correctly uses await using proc.

Suggested change
const proc = Bun.spawn({
await using proc = Bun.spawn({

@robobun robobun force-pushed the claude/fix-26747-server-stop-method branch from 2b9f0c0 to 7b144a2 Compare February 5, 2026 06:51
@robobun
Copy link
Collaborator Author

robobun commented Feb 5, 2026

Updated the test to address review feedback:

  1. Changed the first test to use await using proc for RAII-style cleanup instead of manual proc.kill() in a finally block
  2. Replaced the hard setTimeout timeout with having the server script call process.exit(0) after a short delay, allowing the process to exit naturally so we can verify the startup message was printed

Both tests now use consistent RAII patterns for process cleanup.

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

🤖 Fix all issues with AI agents
In `@test/regression/issue/26747.test.ts`:
- Around line 25-31: The comment above the setTimeout block is inaccurate: it
says "unref it so the process can exit" but the code calls process.exit(0)
inside setTimeout; update the comment to accurately describe that the test
forces process termination after 100ms rather than using server.unref(),
referencing the setTimeout(() => { process.exit(0); }, 100) snippet so future
readers know the process is being forcibly exited.

Comment on lines 26 to 31
// This allows us to check the startup message without the server blocking forever
setTimeout(() => {
// The server is already started by the entry point wrapper
// We can safely exit after checking
process.exit(0);
}, 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

CLAUDE.md violation: This test uses setTimeout in the fixture code, which violates the CLAUDE.md rule:

CRITICAL: Do not write flaky tests. Do not use setTimeout in tests. Instead, await the condition to be met.

The 100ms delay creates a timing-dependent test that could be flaky. Instead, consider having the test:

  1. Read stdout incrementally and look for "Started"
  2. Kill the process once the expected output is found

This avoids the arbitrary timing assumption. Example approach:

Suggested change
// This allows us to check the startup message without the server blocking forever
setTimeout(() => {
// The server is already started by the entry point wrapper
// We can safely exit after checking
process.exit(0);
}, 100);
// After the server starts, unref it so the process can exit
// This allows us to check the startup message without the server blocking forever
process.exit(0); // Exit immediately after startup
`,

Or, modify the test to stream stdout and kill the process once "Started" is detected rather than relying on the fixture to exit itself.

The fix for #26142 incorrectly used `typeof def.stop !== 'function'`
to detect Server instances returned by Bun.serve(). This caused server
config objects with a custom `stop` method (e.g., Elysia apps) to not
auto-start.

Changed the detection to use `typeof def.reload !== 'function'` instead,
since `reload` is specific to Server instances and unlikely to be on
user config objects.

Fixes #26747

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@robobun robobun force-pushed the claude/fix-26747-server-stop-method branch from 7b144a2 to 95c759a Compare February 5, 2026 07:03
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.

export default syntax of HTTP server won't work if the exported object has a stop method

1 participant