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

  1. 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
  2. Verify the planId is a valid, non-empty id from a trusted source before calling GetPlan
  3. Confirm the plans table schema matches the Plan struct after recent migrations
  4. 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

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


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