apolloconfig/apollo · error · AccessDeniedException

Forbidden operation. Caused by: 1.you don't have release per

Error message

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

What it means

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.

Source

Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceBranchController.java:95

  @PreAuthorize(
      value = "@openapiNamespaceBranchController.canCreateBranch(#appId, #env, #clusterName, #namespaceName)")
  @ApolloAuditLog(type = OpType.CREATE, name = "NamespaceBranch.create")
  @Override
  public ResponseEntity<OpenNamespaceDTO> createBranch(String appId, String env, String clusterName,
      String namespaceName, String operator) {
    String resolvedOperator = resolveOperator(operator, null);
    NamespaceDTO namespaceDTO = namespaceBranchService.createBranch(appId, Env.valueOf(env),
        clusterName, namespaceName, resolvedOperator);
    return namespaceDTO == null ? ResponseEntity.ok().build()
        : ResponseEntity.ok(OpenApiModelConverters.fromNamespaceDTO(namespaceDTO));
  }

  @Override
  @ApolloAuditLog(type = OpType.DELETE, name = "NamespaceBranch.delete")
  public ResponseEntity<Void> deleteBranch(String env, String appId, String clusterName,
      String namespaceName, String branchName, String operator) {
    if (!canDeleteBranch(appId, env, clusterName, namespaceName, branchName)) {
      throw new AccessDeniedException(
          "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");
    }
    String resolvedOperator = resolveOperator(operator, null);
    namespaceBranchService.deleteBranch(appId, Env.valueOf(env), clusterName, namespaceName,
        branchName, resolvedOperator);
    return ResponseEntity.ok().build();
  }

  @Override
  public ResponseEntity<OpenNamespaceDTO> findBranch(String appId, String env, String clusterName,
      String namespaceName, Boolean extendInfo) {
    requireConfigReadForUserToken(appId, env, clusterName, namespaceName);
    NamespaceBO namespaceBO =
        namespaceBranchService.findBranch(appId, Env.valueOf(env), clusterName, namespaceName);
    if (namespaceBO == null) {
      return ResponseEntity.ok().build();

View on GitHub (pinned to d95fc18d11)

Solutions

  1. 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.
  2. Grant the caller ReleaseNamespace permission on the namespace so canDeleteBranch succeeds via the release-permission path.
  3. If deletion of an unreleased branch was intended, grant ModifyNamespace permission to the caller.
  4. Re-check branch release state with the find-branch / latest-release endpoint before issuing DELETE.

Example fix

// before: caller only has ModifyNamespace, branch already released
client.deleteBranch(appId, env, cluster, ns, branch); // 403

// after: verify release state, then either grant ReleaseNamespace or skip
ReleaseDTO latest = client.loadLatestRelease(appId, env, branch, ns);
if (latest != null) {
    // released branch cannot be deleted; grant ReleaseNamespace or abandon
} else {
    client.deleteBranch(appId, env, cluster, ns, branch);
}
Defensive patterns

Strategy: validation

Validate before calling

// Before DELETE branch: check release state and permission paths.
boolean hasRelease = hasPermission(token, appId, env, cluster, ns, "ReleaseNamespace");
boolean hasModify = hasPermission(token, appId, env, cluster, ns, "ModifyNamespace");
ReleaseDTO latest = client.loadLatestRelease(appId, env, branch, ns); // null if none
boolean deletable = hasRelease || (hasModify && latest == null);
if (!deletable) { /* grant permission or skip delete; do NOT call deleteBranch */ }

Type guard

null

Try / catch

try {
  client.deleteBranch(appId, env, cluster, ns, branch, operator);
} catch (HttpServerErrorException.Forbidden e) {
  // message lists 3 causes; decide grant-vs-skip based on release state
  if (branchReleased) log.warn("Released branch cannot be deleted");
  else admin.grantReleaseNamespace(appId, ns, operator);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14). Data as JSON: /api/errors/7c406b2ce7da75b2. Report an issue: GitHub.