plandex-ai/plandex · warning

plan %s branch %s already has an active stream on host %s

Error message

plan %s branch %s already has an active stream on host %s

What it means

activatePlan also consults the persisted active model stream: if db.GetActiveModelStream returns a non-nil stream, another host (or process) already owns the stream for this plan+branch. It returns this error including that stream's InternalIp so the user can find the host holding the stream.

Source

Thrown at app/server/model/plan/activate.go:46

	log.Println("Waiting 100ms before checking for active plan")
	time.Sleep(100 * time.Millisecond)
	log.Println("Done waiting, checking for active plan")

	active := GetActivePlan(plan.Id, branch)
	if active != nil {
		log.Printf("Tell: Active plan found for plan ID %s on branch %s\n", plan.Id, branch) // Log if an active plan is found
		return nil, fmt.Errorf("plan %s branch %s already has an active stream on this host", plan.Id, branch)
	}

	modelStream, err := db.GetActiveModelStream(plan.Id, branch)
	if err != nil {
		log.Printf("Error getting active model stream: %v\n", err)
		return nil, fmt.Errorf("error getting active model stream: %v", err)
	}

	if modelStream != nil {
		log.Printf("Tell: Active model stream found for plan ID %s on branch %s on host %s\n", plan.Id, branch, modelStream.InternalIp) // Log if an active model stream is found
		return nil, fmt.Errorf("plan %s branch %s already has an active stream on host %s", plan.Id, branch, modelStream.InternalIp)
	}

	active = CreateActivePlan(
		auth.OrgId,
		auth.User.Id,
		plan.Id,
		branch,
		prompt,
		buildOnly,
		autoContext,
		sessionId,
	)

	modelStream = &db.ModelStream{
		OrgId:      auth.OrgId,
		PlanId:     plan.Id,
		InternalIp: host.Ip,
		Branch:     branch,

View on GitHub (pinned to e2d772072e)

Solutions

  1. Go to the host identified in the error (InternalIp) and stop/cancel the existing stream, then retry.
  2. If the owning host is dead, manually delete the stale model stream record and retry activation.
  3. Add host liveness/TTL checks so dead hosts' stream rows are reaped automatically.
  4. Route the request to the host that already owns the stream instead of activating locally.

Example fix

// before: stale row blocks forever
// after: reap stale streams before activation
if err := db.DeleteStaleModelStreams(plan.Id, branch, staleAfter); err != nil { return err }
modelStream, err := db.GetActiveModelStream(plan.Id, branch)
Defensive patterns

Strategy: validation

Validate before calling

stream, err := db.GetActiveModelStream(planId, branch)
if err == nil && stream != nil {
    return fmt.Errorf("stream already active on host %s; route there or cancel it first", stream.InternalIp)
}
// safe to activate

Try / catch

_, err := Tell(ctx, req)
if err != nil {
    var hostErr *HostActiveError
    if errors.As(err, &hostErr) {
        return proxyToHost(ctx, hostErr.InternalIp, req)
    }
    return err
}

Prevention

When it happens

Trigger: Calling activatePlan when a model stream row for plan.Id + branch still exists (active on another host at modelStream.InternalIp), typically from a prior run that never completed cleanup.

Common situations: Multi-host deployments where another instance owns the branch, crashed hosts leaving orphaned stream rows, or restarts before StreamDoneCh was processed and the row deleted.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/bf9343b193cf4829. Report an issue: GitHub.