{"record":{"id":"44f1c87ecf810a3d","repo":"plandex-ai/plandex","slug":"user-does-not-have-permission-to-delete-plan","errorCode":null,"errorMessage":"User does not have permission to delete plan","messagePattern":"User does not have permission to delete plan","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"app/server/handlers/auth_helpers.go","lineNumber":714,"sourceCode":"\tif plan.OwnerId != auth.User.Id && !auth.HasPermission(shared.PermissionUpdateAnyPlan) {\n\t\tlog.Println(\"User does not have permission to update plan\")\n\t\thttp.Error(w, \"User does not have permission to update plan\", http.StatusForbidden)\n\t\treturn nil\n\t}\n\n\treturn plan\n}\n\nfunc authorizePlanDelete(w http.ResponseWriter, planId string, auth *types.ServerAuth) *db.Plan {\n\tplan := authorizePlan(w, planId, auth)\n\n\tif plan == nil {\n\t\treturn nil\n\t}\n\n\tif plan.OwnerId != auth.User.Id && !auth.HasPermission(shared.PermissionDeleteAnyPlan) {\n\t\tlog.Println(\"User does not have permission to delete plan\")\n\t\thttp.Error(w, \"User does not have permission to delete plan\", http.StatusForbidden)\n\t\treturn nil\n\t}\n\n\treturn plan\n}\n\nfunc authorizePlanRename(w http.ResponseWriter, planId string, auth *types.ServerAuth) *db.Plan {\n\tplan := authorizePlan(w, planId, auth)\n\n\tif plan == nil {\n\t\treturn nil\n\t}\n\n\tif plan.OwnerId != auth.User.Id && !auth.HasPermission(shared.PermissionRenameAnyPlan) {\n\t\tlog.Println(\"User does not have permission to rename plan\")\n\t\thttp.Error(w, \"User does not have permission to rename plan\", http.StatusForbidden)\n\t\treturn nil\n\t}","sourceCodeStart":696,"sourceCodeEnd":732,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/auth_helpers.go#L696-L732","documentation":"This 403 is returned by authorizePlanDelete when the plan is accessible but the requester is neither the plan owner nor a holder of shared.PermissionDeleteAnyPlan. Deleting a plan is reserved for its owner or users with the delete-any-plan admin permission.","triggerScenarios":"DeletePlanHandler invoked by a non-owner member lacking PermissionDeleteAnyPlan, issuing DELETE for a plan owned by someone else.","commonSituations":"A team member tries to remove a plan created by a former employee; cleanup scripts running under a non-privileged token; UI still shows delete buttons after role changes.","solutions":["Have the plan owner or an admin with PermissionDeleteAnyPlan delete the plan","Grant the user/service account PermissionDeleteAnyPlan if policy allows","Reassign ownership before deletion if the owner is offboarded","Gate delete actions in the client on ownership or the delete permission"],"exampleFix":"// before\nawait api.deletePlan(planId);\n// after\nif (plan.ownerId !== auth.userId && !auth.permissions.includes('delete_any_plan')) {\n  throw new Error('Only the plan owner or an admin can delete this plan');\n}\nawait api.deletePlan(planId);","handlingStrategy":"type-guard","validationCode":"function canDeletePlan(plan, auth) {\n  return plan.ownerId === auth.userId || auth.permissions.includes('delete_any_plan');\n}","typeGuard":"function isPlanOwner(plan, auth) {\n  return typeof plan?.ownerId === 'string' && plan.ownerId === auth?.userId;\n}","tryCatchPattern":"try {\n  await api.deletePlan(planId);\n} catch (e) {\n  if (e.status === 403 && /permission to delete plan/.test(e.body)) {\n    notifyUser('Only the plan owner or an admin can delete this plan');\n    return;\n  }\n  throw e;\n}","preventionTips":["Show delete actions only to owners or delete-any-plan holders","Add confirmation step before delete calls","Reassign ownership before offboarding the owner","Verify token scope for automation that deletes plans"],"tags":["http-403","authorization","permissions","ownership"],"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"}