chenhg5/cc-connect · warning
heartbeat timed out after %v
Error message
heartbeat timed out after %v
What it means
The heartbeat execution in core/heartbeat.go runs the entry's task in a goroutine and waits on a done channel with a timeout. If the task does not finish within the configured timeout, the select falls to the time.After branch and the run is failed with this error. The heartbeat task itself keeps running in the background (it is not cancelled), but the run is recorded as failed.
Source
Thrown at core/heartbeat.go:372
prompt = readHeartbeatMD(entry.workDir)
}
if prompt == "" {
prompt = defaultHeartbeatPrompt
}
slog.Info("heartbeat: executing", "project", entry.project, "session_key", cfg.SessionKey, "prompt_len", len(prompt))
timeout := time.Duration(cfg.TimeoutMins) * time.Minute
done := make(chan error, 1)
go func() {
done <- entry.engine.ExecuteHeartbeat(cfg.SessionKey, prompt, cfg.Silent)
}()
var err error
select {
case err = <-done:
case <-time.After(timeout):
err = fmt.Errorf("heartbeat timed out after %v", timeout)
}
hs.mu.Lock()
entry.runCount++
entry.lastRun = time.Now()
if err != nil {
entry.errorCount++
entry.lastError = err.Error()
slog.Error("heartbeat: execution failed", "project", entry.project, "error", err)
} else {
entry.lastError = ""
slog.Info("heartbeat: execution completed", "project", entry.project)
}
hs.mu.Unlock()
}
const defaultHeartbeatPrompt = `This is a periodic heartbeat check. Please briefly review:
- Any pending tasks or unfinished workView on GitHub (pinned to 4000b2338a)
Solutions
- Increase the heartbeat timeout in the entry configuration
- Add an internal context deadline to the heartbeat task so it can be cancelled properly
- Check what the heartbeat task contacts (process, network) and fix the underlying hang
- Inspect system load or deadlocks in the heartbeat handler
Example fix
// before: no internal deadline, task hangs past timeout
func heartbeat(ctx context.Context) error { return checkAgent() }
// after
defCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
func heartbeat(ctx context.Context) error { return checkAgentWithContext(ctx) } Defensive patterns
Strategy: retry
Validate before calling
// estimate task duration before registering
dur := measureHeartbeatTask()
if dur > timeout { return fmt.Errorf("task (%s) exceeds timeout (%s)", dur, timeout) } Try / catch
if err := hb.TriggerNow(name); err != nil {
if strings.Contains(err.Error(), "timed out after") {
slog.Warn("heartbeat timed out; task may still be running", "err", err)
// backoff and retry on next tick
}
} Prevention
- Give every heartbeat task an internal context deadline shorter than the entry timeout
- Set timeouts generously relative to observed task duration
- Avoid blocking network calls without deadlines inside heartbeat handlers
- Alert on repeated timeout errors — they indicate a hang, not slowness
When it happens
Trigger: TriggerNow or the periodic run loop executes a heartbeat whose task goroutine takes longer than the entry's timeout; e.g. a hung process check, blocked network call, or deadlock inside the heartbeat handler.
Common situations: Heartbeat checks an agent CLI that hangs waiting on stdin; network probe to an unresponsive host without its own deadline; system under heavy load slowing the check past the timeout; timeout configured too aggressively for a normally-slow task.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/5aedc398395873f4.
Report an issue: GitHub.