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 work

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Increase the heartbeat timeout in the entry configuration
  2. Add an internal context deadline to the heartbeat task so it can be cancelled properly
  3. Check what the heartbeat task contacts (process, network) and fix the underlying hang
  4. 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

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.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/5aedc398395873f4. Report an issue: GitHub.