{"record":{"id":"d0cd37a0e217a4f8","repo":"plandex-ai/plandex","slug":"no-access-to-plan","errorCode":null,"errorMessage":"no access to plan","messagePattern":"no access to plan","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"app/server/handlers/auth_helpers.go","lineNumber":682,"sourceCode":"\t}\n\n\treturn true\n}\n\nfunc authorizePlan(w http.ResponseWriter, planId string, auth *types.ServerAuth) *db.Plan {\n\tlog.Println(\"authorizing plan\")\n\n\tplan, err := db.ValidatePlanAccess(planId, auth.User.Id, auth.OrgId)\n\n\tif err != nil {\n\t\tlog.Printf(\"error validating plan membership: %v\\n\", err)\n\t\thttp.Error(w, \"error validating plan membership\", http.StatusInternalServerError)\n\t\treturn nil\n\t}\n\n\tif plan == nil {\n\t\tlog.Println(\"user doesn't have access the plan\")\n\t\thttp.Error(w, \"no access to plan\", http.StatusUnauthorized)\n\t\treturn nil\n\t}\n\n\treturn plan\n}\n\nfunc authorizePlanUpdate(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.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}","sourceCodeStart":664,"sourceCodeEnd":700,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/auth_helpers.go#L664-L700","documentation":"This 401 is returned by authorizePlan when ValidatePlanAccess succeeds but returns a nil plan, meaning the user is not a member of (or has no access to) the plan within their organization. Unlike 814, the lookup worked; the result was 'no access'. Plans are scoped by membership plus org, so cross-org or non-member access is rejected.","triggerScenarios":"Calling plan-scoped endpoints (update/delete/rename/archive via the authorizePlan* wrappers, ListBranchesHandler, CreateBranchHandler) with a planId the user was never added to, a plan in another org, or a plan removed while membership rows were deleted.","commonSituations":"Sharing a plan URL with a teammate who has no membership; switching orgs and reusing an old plan ID; a plan membership revoked but a stale client session still shows the plan; guessing plan IDs in API scripts.","solutions":["Confirm the authenticated user has been added as a member of the plan","Verify the planId belongs to the same organization as the auth token","Have the plan owner add the user to the plan, or use an account with access","Re-fetch the plan list for the current org and use a valid planId"],"exampleFix":"// before\nawait api.createBranch({ planId: urlPlanId });\n// after\nconst plans = await api.listPlans();\nif (!plans.some(p => p.id === urlPlanId)) {\n  throw new Error('You do not have access to this plan; ask the owner to add you as a member');\n}\nawait api.createBranch({ planId: urlPlanId });","handlingStrategy":"validation","validationCode":"async function ensurePlanAccess(planId) {\n  const plans = await api.listPlans();\n  if (!plans.some(p => p.id === planId)) {\n    throw new Error(`No access to plan ${planId}; ask the owner to add you as a member`);\n  }\n}","typeGuard":"function isNoPlanAccess(res) {\n  return res.status === 401;\n}","tryCatchPattern":"try {\n  return await api.getPlan(planId);\n} catch (e) {\n  if (e.status === 401 && /no access to plan/.test(e.body)) {\n    notifyUser('You are not a member of this plan');\n    redirect('/plans');\n    return null;\n  }\n  throw e;\n}","preventionTips":["Fetch plans scoped to the current org/session instead of reusing old IDs","Invalidate cached plan IDs when membership is revoked or org switches","Use invitation/share flows rather than sharing raw plan URLs","Handle 401 from plan endpoints by re-listing accessible plans"],"tags":["http-401","authorization","plan-membership","go"],"backgroundTag":"resource-not-found","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"}