{"record":{"id":"a11431f61c2836d8","repo":"plandex-ai/plandex","slug":"error-getting-plan-v","errorCode":null,"errorMessage":"error getting plan: %v","messagePattern":"error getting plan: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":258,"sourceCode":"\t\tconvoTokens += msg.Tokens\n\t}\n\n\t_, err := Conn.Exec(\"UPDATE branches SET context_tokens = $1, convo_tokens = $2 WHERE plan_id = $3 AND name = $4\", contextTokens, convoTokens, planId, branch)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating plan tokens: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc GetPlan(planId string) (*Plan, error) {\n\tvar plan Plan\n\n\terr := Conn.Get(&plan, \"SELECT * FROM plans WHERE id = $1\", planId)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting plan: %v\", err)\n\t}\n\n\treturn &plan, nil\n}\n\nfunc SetPlanStatus(planId, branch string, status shared.PlanStatus, errStr string) error {\n\t_, err := Conn.Exec(\"UPDATE branches SET status = $1, error = $2 WHERE plan_id = $3 AND name = $4\", status, errStr, planId, branch)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error setting plan status: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc RenamePlan(planId string, name string, tx *sqlx.Tx) error {\n\tvar err error\n\tif tx == nil {","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L240-L276","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nerr := Conn.Get(&plan, \"SELECT * FROM plans WHERE id = $1\", planId)\nif err != nil {\n    return nil, fmt.Errorf(\"error getting plan: %v\", err)\n}\n// after\nerr := Conn.Get(&plan, \"SELECT * FROM plans WHERE id = $1\", planId)\nif err != nil {\n    if errors.Is(err, sql.ErrNoRows) {\n        return nil, ErrPlanNotFound\n    }\n    return nil, fmt.Errorf(\"error getting plan: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"if planId == \"\" {\n    return fmt.Errorf(\"planId must not be empty\")\n}","typeGuard":"func planExists(planId string) bool {\n    var exists bool\n    _ = Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM plans WHERE id = $1)\", planId)\n    return exists\n}","tryCatchPattern":"plan, err := GetPlan(planId)\nif err != nil {\n    if errors.Is(err, sql.ErrNoRows) {\n        http.Error(w, \"plan not found\", http.StatusNotFound)\n        return\n    }\n    http.Error(w, \"internal error\", http.StatusInternalServerError)\n    return\n}","preventionTips":["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)"],"tags":["database","postgresql","go","not-found"],"backgroundTag":"record-not-found","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}