{"record":{"id":"060f7819c4af9548","repo":"plandex-ai/plandex","slug":"only-the-plan-owner-can-delete-a-plan","errorCode":null,"errorMessage":"Only the plan owner can delete a plan","messagePattern":"Only the plan owner can delete a plan","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"app/server/handlers/plans_crud.go","lineNumber":229,"sourceCode":"\tauth := Authenticate(w, r, true)\n\tif auth == nil {\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tplanId := vars[\"planId\"]\n\n\tlog.Println(\"planId: \", planId)\n\n\tplan := authorizePlanDelete(w, planId, auth)\n\n\tif plan == nil {\n\t\treturn\n\t}\n\n\tif plan.OwnerId != auth.User.Id {\n\t\tlog.Println(\"Only the plan owner can delete a plan\")\n\t\thttp.Error(w, \"Only the plan owner can delete a plan\", http.StatusForbidden)\n\t\treturn\n\t}\n\n\tres, err := db.Conn.Exec(\"DELETE FROM plans WHERE id = $1\", planId)\n\n\tif err != nil {\n\t\tlog.Printf(\"Error deleting plan: %v\\n\", err)\n\t\thttp.Error(w, \"Error deleting plan: \"+err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\trowsAffected, err := res.RowsAffected()\n\tif err != nil {\n\t\tlog.Printf(\"Error getting rows affected: %v\\n\", err)\n\t\thttp.Error(w, \"Error getting rows affected: \"+err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/plans_crud.go#L211-L247","documentation":"DeletePlanHandler returns this 403 when the authenticated user is not the plan's owner. Even after passing authorizePlanDelete (org/project level permission), the handler enforces that only plan.OwnerId may delete the plan. This is an intentional authorization policy, surfaced as a plain-text 403.","triggerScenarios":"Authenticated non-owner member calls the DELETE plan endpoint on a teammate's plan; an admin token attempts deletion of a user-created plan without being the owner; ownership never reassigned after the creator left the org.","commonSituations":"Team cleanup scripts running under a shared admin account failing on user-owned plans; former employee's plans frozen because nobody else can delete them; automation using one service account for all org operations.","solutions":["Delete using credentials of the plan owner","Transfer plan ownership to the operating account first (update owner_id via a transfer flow or SQL migration)","Reassign ownership during offboarding so plans remain manageable","If admins should be able to delete, change the handler check to allow org admins — as a policy change, not a client workaround"],"exampleFix":"// before\nif plan.OwnerId != auth.User.Id { /* 403 always for admins */ }\n// after\nif plan.OwnerId != auth.User.Id && !auth.IsAdmin {\n\t\n}\n// (server-side policy change; clients must still use an authorized token)","handlingStrategy":"validation","validationCode":"if plan.OwnerId != currentUser.Id { return errors.New(\"only the plan owner can delete this plan\") }","typeGuard":null,"tryCatchPattern":"resp, err := client.DeletePlan(planId)\nif apiErr, ok := asApiError(resp, err); ok && resp.StatusCode == 403 {\n\t// inform the user they need the owner's credentials or an ownership transfer\n}","preventionTips":["Check plan.ownerId before surfacing delete actions to users","Transfer or reassign ownership in offboarding so plans stay deletable","Run cleanup automation under an account that owns the plans (or add an admin policy)","Treat 403 as an ownership/policy signal, distinct from 404/500"],"tags":["authorization","forbidden","ownership","delete","permissions"],"backgroundTag":"insufficient-permissions","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}