{"record":{"id":"7c406b2ce7da75b2","repo":"apolloconfig/apollo","slug":"forbidden-operation-caused-by-1-you-don-t-have-r","errorCode":null,"errorMessage":"Forbidden operation. Caused by: 1.you don't have release permission or 2. you don't have modification permission or 3. you have modification permission but branch has been released","messagePattern":"Forbidden operation\\. Caused by: 1\\.you don't have release permission or 2\\. you don't have modification permission or 3\\. you have modification permission but branch has been released","errorType":"exception","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceBranchController.java","lineNumber":95,"sourceCode":"  @PreAuthorize(\n      value = \"@openapiNamespaceBranchController.canCreateBranch(#appId, #env, #clusterName, #namespaceName)\")\n  @ApolloAuditLog(type = OpType.CREATE, name = \"NamespaceBranch.create\")\n  @Override\n  public ResponseEntity<OpenNamespaceDTO> createBranch(String appId, String env, String clusterName,\n      String namespaceName, String operator) {\n    String resolvedOperator = resolveOperator(operator, null);\n    NamespaceDTO namespaceDTO = namespaceBranchService.createBranch(appId, Env.valueOf(env),\n        clusterName, namespaceName, resolvedOperator);\n    return namespaceDTO == null ? ResponseEntity.ok().build()\n        : ResponseEntity.ok(OpenApiModelConverters.fromNamespaceDTO(namespaceDTO));\n  }\n\n  @Override\n  @ApolloAuditLog(type = OpType.DELETE, name = \"NamespaceBranch.delete\")\n  public ResponseEntity<Void> deleteBranch(String env, String appId, String clusterName,\n      String namespaceName, String branchName, String operator) {\n    if (!canDeleteBranch(appId, env, clusterName, namespaceName, branchName)) {\n      throw new AccessDeniedException(\n          \"Forbidden operation. Caused by: 1.you don't have release permission \"\n              + \"or 2. you don't have modification permission \"\n              + \"or 3. you have modification permission but branch has been released\");\n    }\n    String resolvedOperator = resolveOperator(operator, null);\n    namespaceBranchService.deleteBranch(appId, Env.valueOf(env), clusterName, namespaceName,\n        branchName, resolvedOperator);\n    return ResponseEntity.ok().build();\n  }\n\n  @Override\n  public ResponseEntity<OpenNamespaceDTO> findBranch(String appId, String env, String clusterName,\n      String namespaceName, Boolean extendInfo) {\n    requireConfigReadForUserToken(appId, env, clusterName, namespaceName);\n    NamespaceBO namespaceBO =\n        namespaceBranchService.findBranch(appId, Env.valueOf(env), clusterName, namespaceName);\n    if (namespaceBO == null) {\n      return ResponseEntity.ok().build();","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apolloconfig/apollo/blob/d95fc18d112589efc09ddcbe1507047584d55251/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceBranchController.java#L77-L113","documentation":"HTTP 403 (AccessDeniedException). Thrown by NamespaceBranchController.deleteBranch when canDeleteBranch returns false. Deletion is permitted only if the caller has ReleaseNamespace permission, OR has ModifyNamespace permission AND the branch has never been released (loadLatestRelease returns null). All three failure causes map to this single message: (1) no release permission, (2) no modify permission, (3) has modify permission but a release already exists on the branch.","triggerScenarios":"DELETE /openapi/v1/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName} by a caller holding only ModifyNamespace (not Release) rights on a branch that already has at least one published release; or by a caller holding neither release nor modify permission at all.","commonSituations":"Trying to delete a gray-release branch that has already been used for an active canary release (Apollo forbids deleting released branches to preserve audit history); a token scoped only to 'Modify config' attempting branch lifecycle operations; cleanup automation running under a token whose roles were narrowed.","solutions":["If the branch has been released, do not delete it (Apollo protects released branches); instead abandon the branch or roll back releases first via the release API.","Grant the caller ReleaseNamespace permission on the namespace so canDeleteBranch succeeds via the release-permission path.","If deletion of an unreleased branch was intended, grant ModifyNamespace permission to the caller.","Re-check branch release state with the find-branch / latest-release endpoint before issuing DELETE."],"exampleFix":"// before: caller only has ModifyNamespace, branch already released\nclient.deleteBranch(appId, env, cluster, ns, branch); // 403\n\n// after: verify release state, then either grant ReleaseNamespace or skip\nReleaseDTO latest = client.loadLatestRelease(appId, env, branch, ns);\nif (latest != null) {\n    // released branch cannot be deleted; grant ReleaseNamespace or abandon\n} else {\n    client.deleteBranch(appId, env, cluster, ns, branch);\n}","handlingStrategy":"validation","validationCode":"// Before DELETE branch: check release state and permission paths.\nboolean hasRelease = hasPermission(token, appId, env, cluster, ns, \"ReleaseNamespace\");\nboolean hasModify = hasPermission(token, appId, env, cluster, ns, \"ModifyNamespace\");\nReleaseDTO latest = client.loadLatestRelease(appId, env, branch, ns); // null if none\nboolean deletable = hasRelease || (hasModify && latest == null);\nif (!deletable) { /* grant permission or skip delete; do NOT call deleteBranch */ }","typeGuard":"null","tryCatchPattern":"try {\n  client.deleteBranch(appId, env, cluster, ns, branch, operator);\n} catch (HttpServerErrorException.Forbidden e) {\n  // message lists 3 causes; decide grant-vs-skip based on release state\n  if (branchReleased) log.warn(\"Released branch cannot be deleted\");\n  else admin.grantReleaseNamespace(appId, ns, operator);\n}","preventionTips":["Never attempt to delete a branch that has been released; Apollo protects it.","Grant ReleaseNamespace to any automation that manages branch lifecycle.","Always fetch latest-release state before issuing deleteBranch."],"tags":["apollo-portal","openapi","permission","access-denied","namespace-branch","release"],"backgroundTag":null,"analyzedSha":"d95fc18d112589efc09ddcbe1507047584d55251","analyzedAt":"2026-08-14T04:00:05.477Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}