plandex-ai/plandex · error
Stream has no branch
Error message
Stream has no branch
What it means
ListPlansRunningHandler builds a map of branches keyed by PlanId|BranchName from db.ListBranchesForPlans, then iterates active/recent model streams. If a stream references a (planId, branch) pair not present among the fetched branches, the handler aborts with this 500 instead of returning partial data. It is a referential-integrity assertion: every stream is expected to have a matching non-archived branch row.
Source
Thrown at app/server/handlers/plans_crud.go:518
var apiPlansById = make(map[string]*shared.Plan)
for _, plan := range plans {
apiPlan := plan.ToApi()
apiPlansById[plan.Id] = apiPlan
}
var apiBranchesByComposite = make(map[string]*shared.Branch)
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.FinishedAtView on GitHub (pinned to e2d772072e)
Solutions
- Find the orphaned stream: the server log line 'Stream <id> has no branch' gives the stream Id; delete or finish that stream row in the model_streams table
- Restore/recreate the missing branch for that planId with the exact name recorded on the stream
- Check whether the stream is stale (still active but actually dead) and mark it finished/archived so GetActiveModelStreams stops returning it
- Reproduce locally by comparing SELECT plan_id, branch FROM model_streams WHERE active against the branch list for those plans; align the two queries' filters (archived/deleted, planIds)
- Code-level fix: skip orphaned streams with a log warning instead of aborting the whole listing
Example fix
// before
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
}
// after
apiBranch, ok := apiBranchesByComposite[branchComposite]
if !ok {
log.Printf("Stream %s has no branch; skipping\n", stream.Id)
continue
} Defensive patterns
Strategy: type-guard
Validate before calling
// Guard: only treat a stream as listable if its branch still exists
for _, s := range streams {
if !branchExists(branches, s.PlanId, s.Branch) {
log.Printf("skipping stream %s: branch %s missing", s.Id, s.Branch)
}
} Type guard
func streamHasBranch(stream *db.ModelStream, branches []*db.Branch) bool {
for _, b := range branches {
if b.PlanId == stream.PlanId && b.Name == stream.Branch {
return true
}
}
return false
} 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 branch") {
// refresh branch list / re-open session before retrying
}
} Prevention
- Never hard-delete or rename a branch while a stream is active; mark streams finished first
- Enforce FK or application-level check between model_streams.plan_id/branch and branches
- Periodically sweep stale active streams whose branches no longer exist
- When deleting/archiving branches, cascade-close their running streams
When it happens
Trigger: A model_stream row exists whose stream.Branch does not match any branch name for stream.PlanId returned by ListBranchesForPlans — e.g. the branch was renamed or deleted/archived while its stream is still marked active, or GetActiveModelStreams and ListBranchesForPlans were called with inconsistent planIds/filters.
Common situations: A branch was deleted (or archived — the branch query may exclude archived/deleted rows) while a stream on it was still running; branch rename during an active coding session; an org/plan scoping mismatch (branches fetched with auth.OrgId but streams not org-scoped); soft-deleted plan still having active streams.
Related errors
- Stream has no plan
- token exchange failed - error creating request: %s
- token exchange failed - error reading body: %s
- token exchange failed - status: %d, body: %s
- failed to update context: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/678f33438f70180f.
Report an issue: GitHub.