plandex-ai/plandex · warning
no active plan with id %s
Error message
no active plan with id %s
What it means
Stop attempts to cancel a currently active plan by looking it up in the in-memory registry. If no plan with that ID + branch is active, it returns 'no active plan with id %s'. The plan may have already finished, been stopped, or never started on this server.
Source
Thrown at app/server/model/plan/stop.go:14
package plan
import (
"fmt"
"plandex-server/db"
"github.com/sashabaranov/go-openai"
)
func Stop(planId, branch, currentUserId, currentOrgId string) error {
active := GetActivePlan(planId, branch)
if active == nil {
return fmt.Errorf("no active plan with id %s", planId)
}
active.SummaryCancelFn()
active.CancelFn()
return nil
}
func StorePartialReply(repo *db.GitRepo, planId, branch, currentUserId, currentOrgId string) error {
active := GetActivePlan(planId, branch)
if active == nil {
return fmt.Errorf("no active plan with id %s", planId)
}
if !active.BuildOnly && !active.RepliesFinished {
num := active.MessageNum + 1
View on GitHub (pinned to e2d772072e)
Solutions
- Check the plan's status first (GET plan) — if it already finished/errored, no stop is needed
- Verify planId and branch exactly match the running plan
- Treat this error as idempotent no-op in clients: if the plan is not active, stop succeeded trivially
- In multi-replica deployments, route stop requests to the same instance holding the active plan (sticky sessions)
Example fix
// before
if err := plan.Stop(planId, branch, userId, orgId); err != nil {
return err
}
// after: tolerate already-stopped plans
if err := plan.Stop(planId, branch, userId, orgId); err != nil && !strings.Contains(err.Error(), "no active plan") {
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// check the plan status before attempting to stop
status := getPlanStatus(planId, branch)
if status != "active" && status != "streaming" {
return nil // already finished; stop is a no-op
} Type guard
func isActive(p *types.ActivePlan) bool { return p != nil } Try / catch
if err := plan.Stop(planId, branch, userId, orgId); err != nil {
if strings.Contains(err.Error(), "no active plan with id") {
return nil // idempotent stop: plan already finished
}
return err
} Prevention
- Treat stop as idempotent — a missing active plan means the goal is achieved
- Always pass the exact planId and branch from the original request
- Re-fetch plan state after server restarts instead of assuming liveness
- In multi-replica setups, use sticky routing for stream lifecycle calls
When it happens
Trigger: Calling Stop (POST /plans/{id}/{branch}/stop) after the plan already completed; double-stop race where a first Stop already canceled and deregistered the plan; planId/branch mismatch (wrong branch name); server restarted so in-memory active plans were lost; calling Stop on a plan running on a different server instance.
Common situations: UI showing a plan as running due to stale state while it already finished; load-balanced deployments without sticky sessions hitting a replica that doesn't hold the active plan; typos in branch names in API calls.
Related errors
- error listing contexts: %v
- error getting context body: %v
- error fetching users: %s
- error fetching pending invites: %s
- error getting custom model packs: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/afd424785ae99dd0.
Report an issue: GitHub.