plandex-ai/plandex · error
active plan not found for plan ID %s on branch %s
Error message
active plan not found for plan ID %s on branch %s
What it means
onActivePlanMissingError in tell_stream_error.go centralizes handling for a missing in-memory active plan during an active tell stream. When any stream handler (processChunk, handleMissingFile, handleDescAndExecStatus, handleStreamFinished) finds GetActivePlan(planId, branch) nil, this method logs and routes the formatted 'active plan not found for plan ID %s on branch %s' error through state.onError with storeDesc=true so the description is persisted.
Source
Thrown at app/server/model/plan/tell_stream_error.go:267
active.StreamDoneCh <- &shared.ApiError{
Type: shared.ApiErrorTypeOther,
Status: http.StatusInternalServerError,
Msg: msg,
}
}
return onErrorResult{
shouldContinueMainLoop: true,
}
}
func (state *activeTellStreamState) onActivePlanMissingError() {
planId := state.plan.Id
branch := state.branch
log.Printf("Active plan not found for plan ID %s on branch %s\n", planId, branch)
state.onError(onErrorParams{
streamErr: fmt.Errorf("active plan not found for plan ID %s on branch %s", planId, branch),
storeDesc: true,
})
}
View on GitHub (pinned to e2d772072e)
Solutions
- Check server logs for which handler triggered it and whether a prior finish already removed the plan
- Ensure only one code path clears the active plan (guard with sync.Once or state flag)
- Compare branch strings at stream start vs chunk processing
- If caused by a stale client stream, close the connection instead of retrying
Example fix
// before
func (state *activeTellStreamState) onActivePlanMissingError() {
state.onError(onErrorParams{
streamErr: fmt.Errorf("active plan not found for plan ID %s on branch %s", planId, branch),
storeDesc: true,
})
}
// after
func (state *activeTellStreamState) onActivePlanMissingError() {
if state.cleanedUp.Load() { // already finished; don't double-handle
return
}
state.onError(onErrorParams{
streamErr: fmt.Errorf("active plan not found for plan ID %s on branch %s", planId, branch),
storeDesc: true,
})
} Defensive patterns
Strategy: validation
Validate before calling
if GetActivePlan(state.plan.Id, state.branch) == nil {
// skip chunk processing entirely; stream is already torn down
return
} Type guard
func (state *activeTellStreamState) hasActivePlan() bool {
return GetActivePlan(state.plan.Id, state.branch) != nil
} Try / catch
if GetActivePlan(planId, branch) == nil {
state.onActivePlanMissingError()
return
} Prevention
- Guard every chunk handler with an active-plan existence check
- Use a single owner for active-plan lifecycle (register/evict)
- Log planId+branch on registration and eviction to spot mismatches
- Avoid concurrent finish paths racing on the same active plan
When it happens
Trigger: Active plan evicted/removed mid-stream (stream finished on another path), branch identifier mismatch between stream start and chunk processing, concurrent stream-finish paths racing to clear the active plan, or server state loss.
Common situations: Double handling of a finished stream; client retries chunks after the active plan was cleaned up; branch name casing/prefix differs between registration and lookup.
Related errors
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/ce3977f285ce8457.
Report an issue: GitHub.