Description
When running two instances of the TeamCode application in different directories, the second instance freezes during startup if the Go core rewrite is enabled. This is a critical regression introduced by the Go core rewrite feature.
Root Cause
The Go core server (go-core/cmd/server/main.go) listens on a fixed port defined by the GO_CORE_PORT environment variable, defaulting to 43001:
port := os.Getenv("GO_CORE_PORT")
if port == "" {
port = "43001"
}
// ...
server := &http.Server{Addr: ":" + port}
When the first instance starts, its Go core process successfully binds to :43001. When the second instance starts, its Go core process fails to bind because the port is already in use (EADDRINUSE). The Go process exits with an error, but the TypeScript spawner (packages/core/src/router/go-core.ts) does not detect this failure gracefully.
The Spawner Problem
In go-core.ts, the startGoCore() function:
- Spawns the Go binary with
GO_CORE_PORT=43001 (line 78)
- Enters a 5-second polling loop waiting for the health endpoint (lines 97-112)
- Since the Go process already crashed, it never gets a health response
- After 5 seconds, it calls
stopGoCore() and returns false
During those 5 seconds, the application appears frozen — no error is shown, no fallback to TypeScript is triggered immediately. After the timeout, Go core features are unavailable but the application may enter an inconsistent state where some routes are routed to a dead Go core while others use TypeScript, depending on feature flag configuration.
Additional Issues
-
No port conflict detection: The Go core binary should detect port conflicts and exit with a specific error code that the TS spawner can interpret immediately (instead of waiting 5 seconds).
-
No isolated port per instance: Each application instance should use its own port (e.g., derived from PID or a random available port).
-
No graceful fallback: When Go core fails to start, all Go-routed operations should immediately fall back to the TypeScript implementation instead of hanging.
-
No error propagation: The startGoCore() function does not capture or relay the Go process error output to the user.
Steps to Reproduce
- Enable Go core rewrite (set
GO_CORE_BINARY env var or have the binary bundled)
- Open two terminal windows
- In terminal 1:
teamcode (or bun dev) in /path/to/project-a
- In terminal 2, immediately:
teamcode (or bun dev) in /path/to/project-b
- Observe that the second instance freezes for ~5 seconds and may remain partially functional
Expected Behavior
Each application instance should:
- Use an independent port for its Go core process
- Gracefully fall back to TypeScript if Go core fails to start
- Show a meaningful error message if Go core cannot start
Environment
- Go core version: current
dev branch
- Application: TeamCode (TypeScript + Go core rewrite)
- Platform: Linux/macOS/Windows (all affected)
Suggested Fix
-
Use a dynamic port — allocate a random available port per Go core process instead of hardcoding 43001:
- Pass
--port 0 to have the OS assign a free port, then read the actual port from stdout
- Or use
get-port (or equivalent) to find a free port before spawning
-
Detect port conflict immediately — the Go binary should exit with code EADDRINUSE (or a specific exit code) when port binding fails, so the TS spawner can react immediately.
-
Fail fast and fall back — if Go core cannot start within ~1 second, log the error and fall back to TypeScript immediately.
-
Show error to user — print a clear error message when Go core fails to start.
Description
When running two instances of the TeamCode application in different directories, the second instance freezes during startup if the Go core rewrite is enabled. This is a critical regression introduced by the Go core rewrite feature.
Root Cause
The Go core server (
go-core/cmd/server/main.go) listens on a fixed port defined by theGO_CORE_PORTenvironment variable, defaulting to43001:When the first instance starts, its Go core process successfully binds to
:43001. When the second instance starts, its Go core process fails to bind because the port is already in use (EADDRINUSE). The Go process exits with an error, but the TypeScript spawner (packages/core/src/router/go-core.ts) does not detect this failure gracefully.The Spawner Problem
In
go-core.ts, thestartGoCore()function:GO_CORE_PORT=43001(line 78)stopGoCore()and returnsfalseDuring those 5 seconds, the application appears frozen — no error is shown, no fallback to TypeScript is triggered immediately. After the timeout, Go core features are unavailable but the application may enter an inconsistent state where some routes are routed to a dead Go core while others use TypeScript, depending on feature flag configuration.
Additional Issues
No port conflict detection: The Go core binary should detect port conflicts and exit with a specific error code that the TS spawner can interpret immediately (instead of waiting 5 seconds).
No isolated port per instance: Each application instance should use its own port (e.g., derived from PID or a random available port).
No graceful fallback: When Go core fails to start, all Go-routed operations should immediately fall back to the TypeScript implementation instead of hanging.
No error propagation: The
startGoCore()function does not capture or relay the Go process error output to the user.Steps to Reproduce
GO_CORE_BINARYenv var or have the binary bundled)teamcode(orbun dev) in/path/to/project-ateamcode(orbun dev) in/path/to/project-bExpected Behavior
Each application instance should:
Environment
devbranchSuggested Fix
Use a dynamic port — allocate a random available port per Go core process instead of hardcoding
43001:--port 0to have the OS assign a free port, then read the actual port from stdoutget-port(or equivalent) to find a free port before spawningDetect port conflict immediately — the Go binary should exit with code
EADDRINUSE(or a specific exit code) when port binding fails, so the TS spawner can react immediately.Fail fast and fall back — if Go core cannot start within ~1 second, log the error and fall back to TypeScript immediately.
Show error to user — print a clear error message when Go core fails to start.