plandex-ai/plandex · error
connection to plan stream timed out due to missing heartbeat
Error message
connection to plan stream timed out due to missing heartbeats
What it means
This error is produced by connectPlanRespStream in app/cli/api/stream.go when the plan SSE-style response stream receives no messages (including heartbeat frames) for HeartbeatTimeout (16 seconds, roughly 3 missed heartbeats). The reader goroutine arms a timer and resets it on every message; if the timer fires, it reports this error through the onStream callback and closes the HTTP response body. It indicates the server stopped sending data or the connection silently stalled without an I/O error.
Source
Thrown at app/cli/api/stream.go:29
"time"
shared "plandex-shared"
)
// 3 heartbeat misses = timeout
const HeartbeatTimeout = 16 * time.Second
func connectPlanRespStream(body io.ReadCloser, onStream types.OnStreamPlan) {
reader := bufio.NewReader(body)
timer := time.NewTimer(HeartbeatTimeout)
defer timer.Stop()
go func() {
for {
select {
case <-timer.C:
log.Println("Connection to plan stream timed out due to missing heartbeats")
onStream(types.OnStreamPlanParams{Msg: nil, Err: fmt.Errorf("connection to plan stream timed out due to missing heartbeats")})
body.Close()
return
default:
}
s, err := readUntilSeparator(reader, shared.STREAM_MESSAGE_SEPARATOR)
if err != nil {
log.Println("Error reading line:", err)
onStream(types.OnStreamPlanParams{Msg: nil, Err: err})
body.Close()
return
}
timer.Reset(HeartbeatTimeout)
// ignore heartbeats
if s == string(shared.StreamMessageHeartbeat) {
continueView on GitHub (pinned to e2d772072e)
Solutions
- Check network path (VPN, proxy, corporate firewall) for idle/streaming connection timeouts and raise or disable them for the API host.
- Retry the plan request — transient server stalls usually resolve; enable any available retry/resume logic in the CLI.
- Verify the Plandex server is healthy and not overloaded (restart or scale the server) since stalled heartbeats often mean the backend hung.
- Upgrade both CLI and server versions to ensure heartbeat intervals and timeout (HeartbeatTimeout=16s) are compatible.
Defensive patterns
Strategy: retry
Validate before calling
// Before starting a long plan stream, verify reachability and avoid idle-killing intermediaries
if err := checkServerReachable(serverURL); err != nil {
return fmt.Errorf("server unreachable before streaming: %w", err)
}
// ensure proxies keep the connection alive: disable idle timeouts / enable TCP keepalive on the http.Client transport Type guard
// Narrow the error surfaced via OnStreamPlanParams
func isHeartbeatTimeout(err error) bool {
return err != nil && strings.Contains(err.Error(), "timed out due to missing heartbeats")
} Try / catch
// In the OnStreamPlan callback
if params.Err != nil {
if isHeartbeatTimeout(params.Err) {
// close/reconnect: retry the stream or resume the plan rather than treating it as fatal
retryWithBackoff(func() error { return startPlanStream(onStream) })
return
}
handleFatal(params.Err)
} Prevention
- Keep the connection path free of proxies/firewalls with aggressive idle timeouts
- Use TCP keepalive on the HTTP transport used for streaming
- Monitor server health; stalled heartbeats usually mean a hung backend
- Retry transient stream failures with backoff instead of failing the whole plan
When it happens
Trigger: During an active plan stream (connectPlanRespStream), the readUntilSeparator call blocks for >16s with no message arriving — the server never resets the timer, so it fires and the goroutine calls onStream with this error.
Common situations: Server under heavy load or hung LLM backend stops emitting heartbeats; a proxy/firewall/load balancer silently drops or buffers the long-lived streaming connection; network outage that doesn't surface as a read error; server crashed mid-stream without closing the TCP connection.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Error copying response body
- error listing contexts: %v
- error getting context body: %v
- error getting server models input: %v
- error fetching users: %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/60bfa157e41a7cee.
Report an issue: GitHub.