plandex-ai/plandex · error

Stream has no plan

Error message

Stream has no plan

What it means

ListPlansRunningHandler builds apiPlansById from the plans list fetched earlier in the handler, then verifies every stream's PlanId exists in that map. If a stream references a plan that was not returned (deleted, not owned, or filtered out), the handler stops and returns this 500. It is a referential-integrity check: every active stream must belong to a plan visible to the caller.

Source

Thrown at app/server/handlers/plans_crud.go:525

	for _, branch := range branches {
		apiBranch := branch.ToApi()
		apiBranchesByComposite[branch.PlanId+"|"+branch.Name] = apiBranch
	}

	addedBranches := make(map[string]bool)
	for _, stream := range streams {
		branchComposite := stream.PlanId + "|" + stream.Branch
		apiBranch, ok := apiBranchesByComposite[branchComposite]
		if !ok {
			log.Printf("Stream %s has no branch\n", stream.Id)
			http.Error(w, "Stream has no branch", http.StatusInternalServerError)
			return
		}

		apiPlan, ok := apiPlansById[stream.PlanId]
		if !ok {
			log.Printf("Stream %s has no plan\n", stream.Id)
			http.Error(w, "Stream has no plan", http.StatusInternalServerError)
			return
		}

		if !addedBranches[branchComposite] {
			res.Branches = append(res.Branches, apiBranch)
			addedBranches[branchComposite] = true
		}

		res.StreamStartedAtByBranchId[apiBranch.Id] = stream.CreatedAt
		if stream.FinishedAt != nil {
			res.StreamFinishedAtByBranchId[apiBranch.Id] = *stream.FinishedAt
		}
		res.StreamIdByBranchId[apiBranch.Id] = stream.Id

		res.PlansById[stream.PlanId] = apiPlan
	}

	sort.Slice(res.Branches, func(i, j int) bool {

View on GitHub (pinned to e2d772072e)

Solutions

  1. Use the server log line 'Stream <id> has no plan' to identify the orphaned stream, then clean up or finish that stream row
  2. Restore the missing plan or re-associate the stream with a valid planId
  3. Align the plan fetch and stream fetch filters (same project, ownership, and soft-delete exclusions) so the two queries agree
  4. Code-level fix: skip streams whose plan is missing, logging a warning, rather than failing the entire response

Example fix

// before
apiPlan, ok := apiPlansById[stream.PlanId]
if !ok {
    log.Printf("Stream %s has no plan\n", stream.Id)
    http.Error(w, "Stream has no plan", http.StatusInternalServerError)
    return
}
// after
apiPlan, ok := apiPlansById[stream.PlanId]
if !ok {
    log.Printf("Stream %s has no plan; skipping\n", stream.Id)
    continue
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Guard: filter streams to those whose plan is present before building the response
valid := streams[:0]
for _, s := range streams {
    if _, ok := apiPlansById[s.PlanId]; ok {
        valid = append(valid, s)
    }
}

Type guard

func streamHasPlan(stream *db.ModelStream, plansById map[string]*shared.Plan) bool {
    _, ok := plansById[stream.PlanId]
    return ok
}

Try / catch

// Go: client-side, detect the sentinel 500 body
if resp.StatusCode == 500 {
    body, _ := io.ReadAll(resp.Body)
    if strings.Contains(string(body), "Stream has no plan") {
        // re-fetch plan list or clear local session state before retrying
    }
}

Prevention

When it happens

Trigger: A model_stream exists for a planId that is absent from the handler's plan list — e.g. the plan was soft-deleted after the stream started, the plan belongs to a different project/owner than the one queried, or the plan query (ListOwnedPlans/GetPlansByIds) filters it out while GetActiveModelStreams still returns the stream.

Common situations: Deleting a plan while a coding session stream on it is still marked active; plan moved between projects; permission scoping where streams are fetched org-wide but plans only for the requesting user; stale streams never marked finished after a crash.

Related errors


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