plandex-ai/plandex · warning
plan %s branch %s already has an active stream on this host
Error message
plan %s branch %s already has an active stream on this host
What it means
activatePlan enforces single-writer semantics per plan+branch on this host. After a short wait it checks GetActivePlan; if an active plan already exists for the same plan ID and branch, it refuses to start another stream and returns this error. This prevents two concurrent model streams from fighting over the same plan branch.
Source
Thrown at app/server/model/plan/activate.go:35
plan *db.Plan,
branch string,
auth *types.ServerAuth,
prompt string,
buildOnly,
autoContext bool,
sessionId string,
) (*types.ActivePlan, error) {
log.Printf("Activate plan: plan ID %s on branch %s\n", plan.Id, branch)
// Just in case this request was made immediately after another stream finished, wait a little to allow for cleanup
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,View on GitHub (pinned to e2d772072e)
Solutions
- Check if an existing stream is intentional; if it is stale, cancel/deregister it and retry.
- Wait for the current stream to finish (subscribe to its stream events) instead of starting a new one.
- Verify ActivePlan cleanup runs when a stream ends or the process crashes (crash-recovery sweep).
- Serialize client-side: guard against duplicate Tell calls for the same plan/branch.
Example fix
// before: blind retry
resp, err := Tell(ctx, req)
// after: treat 'already active' as expected
resp, err := Tell(ctx, req)
if err != nil && strings.Contains(err.Error(), "already has an active stream") {
return subscribeExisting(ctx, req.PlanId, req.Branch)
} Defensive patterns
Strategy: type-guard
Validate before calling
active := model.GetActivePlan(planId, branch)
if active != nil {
return fmt.Errorf("stream already active for plan %s branch %s", planId, branch)
}
// safe to call Tell Type guard
func isActiveStream(r *shared.StreamRes) bool { return r != nil } Try / catch
resp, err := Tell(ctx, req)
if err != nil && strings.Contains(err.Error(), "already has an active stream on this host") {
resp, err = subscribeToExistingStream(ctx, req.PlanId, req.Branch)
} Prevention
- Deduplicate Tell requests client-side per plan/branch
- Check GetActivePlan before starting a new stream
- Ensure stream cleanup runs on process shutdown and crash recovery
- Surface 'already active' as a distinct error code to the UI
When it happens
Trigger: Calling activatePlan (via Tell or loadPendingBuilds) while another stream for the same plan.Id + branch is still registered as active on this host.
Common situations: Double-submitting a Tell from a retried UI request, a previous stream that crashed without deregistering, or concurrent webhooks/CLI calls triggering the same plan branch.
Related errors
- plan %s branch %s already has an active stream on host %s
- failed to get file info for %s: %v
- failed to read file %s: %v
- failed to load context: %v
- failed to get file map: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/7451bf2f495f1d59.
Report an issue: GitHub.