plandex-ai/plandex · error
error getting plan: %v
Error message
error getting plan: %v
What it means
GetPlan fetches a single plans row by id with Conn.Get(&plan, "SELECT * FROM plans WHERE id = $1") and wraps any error as 'error getting plan'. The most common cause is sql.ErrNoRows (plan id does not exist), but connection failures and scan errors after schema changes also land here. It is heavily used (e.g. by ValidatePlanAccess) so this error frequently reaches HTTP handlers as a 404/500.
Source
Thrown at app/server/db/plan_helpers.go:258
convoTokens += msg.Tokens
}
_, err := Conn.Exec("UPDATE branches SET context_tokens = $1, convo_tokens = $2 WHERE plan_id = $3 AND name = $4", contextTokens, convoTokens, planId, branch)
if err != nil {
return fmt.Errorf("error updating plan tokens: %v", err)
}
return nil
}
func GetPlan(planId string) (*Plan, error) {
var plan Plan
err := Conn.Get(&plan, "SELECT * FROM plans WHERE id = $1", planId)
if err != nil {
return nil, fmt.Errorf("error getting plan: %v", err)
}
return &plan, nil
}
func SetPlanStatus(planId, branch string, status shared.PlanStatus, errStr string) error {
_, err := Conn.Exec("UPDATE branches SET status = $1, error = $2 WHERE plan_id = $3 AND name = $4", status, errStr, planId, branch)
if err != nil {
return fmt.Errorf("error setting plan status: %v", err)
}
return nil
}
func RenamePlan(planId string, name string, tx *sqlx.Tx) error {
var err error
if tx == nil {View on GitHub (pinned to e2d772072e)
Solutions
- Treat sql.ErrNoRows distinctly — check errors.Is(err, sql.ErrNoRows) at the call site and return a 404-style response instead of a generic 500
- Verify the planId is a valid, non-empty id from a trusted source before calling GetPlan
- Confirm the plans table schema matches the Plan struct after recent migrations
- Check DB connectivity/pool if the inner error is a connection error
Example fix
// before
err := Conn.Get(&plan, "SELECT * FROM plans WHERE id = $1", planId)
if err != nil {
return nil, fmt.Errorf("error getting plan: %v", err)
}
// after
err := Conn.Get(&plan, "SELECT * FROM plans WHERE id = $1", planId)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrPlanNotFound
}
return nil, fmt.Errorf("error getting plan: %w", err)
} Defensive patterns
Strategy: type-guard
Validate before calling
if planId == "" {
return fmt.Errorf("planId must not be empty")
} Type guard
func planExists(planId string) bool {
var exists bool
_ = Conn.Get(&exists, "SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)", planId)
return exists
} Try / catch
plan, err := GetPlan(planId)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "plan not found", http.StatusNotFound)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
} Prevention
- Distinguish sql.ErrNoRows from other failures at every GetPlan call site
- Validate planId format/provenance before lookup
- Keep the Plan struct synchronized with the plans table schema
- Handle plan deletion concurrently (re-check before dependent writes)
When it happens
Trigger: Calling GetPlan with a planId that does not exist (or was deleted), a DB connectivity failure, or a plans row that cannot be scanned into the Plan struct due to a schema/struct mismatch.
Common situations: Client caches a planId for a plan deleted on another device; test/dev planId used against a prod DB; concurrent plan deletion between an access check and the fetch; a migration added a non-nullable column the Plan struct cannot scan.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- error getting plan config: %v
- error getting default plan config: %v
- error getting default plan config: %v
- error updating plan total replies: %v
- error setting plan status: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/a11431f61c2836d8.
Report an issue: GitHub.