{"record":{"id":"89684815dbe02eb1","repo":"plandex-ai/plandex","slug":"no-active-plan-for-plan","errorCode":null,"errorMessage":"No active plan for plan","messagePattern":"No active plan for plan","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"app/server/handlers/proxy_helper.go","lineNumber":44,"sourceCode":"\t\tlog.Printf(\"No active model stream for plan %s\\n\", planId)\n\t\thttp.Error(w, \"No active model stream for plan\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tif modelStream.InternalIp == host.Ip {\n\t\t// No active plan for this plan or else we wouldn't be calling proxyActivePlanMethod -- set the model stream to finished because something went wrong\n\t\terr := db.SetModelStreamFinished(modelStream.Id)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error setting model stream %s to finished: %v\\n\", modelStream.Id, err)\n\t\t}\n\n\t\terr = db.SetPlanStatus(planId, branch, shared.PlanStatusError, \"No active stream for plan\")\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error setting plan %s status to error: %v\\n\", planId, err)\n\t\t}\n\n\t\tlog.Printf(\"No active plan for plan %s\\n\", planId)\n\t\thttp.Error(w, \"No active plan for plan\", http.StatusNotFound)\n\t\treturn\n\t} else {\n\t\tlog.Printf(\"Forwarding request to %s\\n\", modelStream.InternalIp)\n\t\tproxyUrl := fmt.Sprintf(\"http://%s:%s/plans/%s/%s/%s\", modelStream.InternalIp, os.Getenv(\"PORT\"), planId, branch, method)\n\t\tproxyUrl += \"?proxy=true\"\n\n\t\tlog.Printf(\"Proxy url: %s\\n\", proxyUrl)\n\t\tproxyRequest(w, r, proxyUrl)\n\t\treturn\n\t}\n}\n\nfunc proxyRequest(w http.ResponseWriter, originalRequest *http.Request, url string) {\n\tclient := &http.Client{\n\t\tTimeout: time.Second * 10,\n\t}\n\n\t// Create a new request based on the original request","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/proxy_helper.go#L26-L62","documentation":"A model stream row exists, but the plan is not in a running state (modelStream.Status is not running/started as expected), so the handler treats the plan as dead: it marks the plan status as error via SetPlanStatus and returns HTTP 404 \"No active plan for plan\". This is a lifecycle-mismatch guard — the stream record is stale relative to the plan's actual state.","triggerScenarios":"Plan crashed or its instance died without a clean SetModelStreamFinished, leaving a non-running stream row; user hits a proxied endpoint for a plan that errored; status column updated by another component while the stream row remained; branch mismatch so the lookup returns a stale row for a different run.","commonSituations":"Instance OOM-killed or node reclaimed, no cleanup ran; deploys restarting instances mid-plan; client polling a finished/errored plan after a crash; concurrent stop requests racing with the status update.","solutions":["The plan was marked error server-side — refresh plan status via the status endpoint and start a new plan run.","Check server logs for why the plan stopped (instance crash, OOM, manual stop) and fix the root cause.","Harden the instance lifecycle so SetModelStreamFinished/SetPlanStatus always run on shutdown (defer, signal handlers, health-check-driven cleanup).","Clients should handle 404 by abandoning the old plan and creating/reconnecting a fresh one.","Verify planId+branch match the actually-running plan to avoid hitting stale rows."],"exampleFix":"// before (client)\nawait connectToPlan(planId, branch); // throws on 404 after a crash\n// after\ntry {\n  await connectToPlan(planId, branch);\n} catch (e) {\n  if (e.status === 404) {\n    await startNewPlan(planId, branch); // stale/errored plan: restart\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"fallback","validationCode":"// client-side: check status before proxied calls\nconst plan = await api.getPlan(planId, branch);\nif (!plan || plan.status !== 'running') { await startNewPlan(planId, branch); return; }","typeGuard":null,"tryCatchPattern":"// client fallback\ntry {\n  await connectToPlan(planId, branch);\n} catch (e) {\n  if (e.status === 404) {\n    // server marked plan errored — fall back to a fresh run\n    await startNewPlan(planId, branch);\n  } else { throw e; }\n}","preventionTips":["Ensure instances run cleanup (SetModelStreamFinished / SetPlanStatus) on shutdown via signal handlers and defers.","Monitor instance crashes (OOM, node reclaim) that leave stale stream rows.","Verify planId+branch correctness to avoid resolving stale rows.","Add a reconciler that marks streams error/finished when heartbeats stop."],"tags":["http-404","plan-lifecycle","stale-state","proxy"],"backgroundTag":"stale-plan-state","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}