SonarSource/sonarqube · error · IllegalArgumentException

Only non-main branches can be deleted

Error message

Only non-main branches can be deleted

What it means

ComponentCleanerService.deleteBranch refuses to delete the main branch of a project: BranchDto.isMain() triggers this IllegalArgumentException. Main branches are deleted implicitly by deleting the project, never directly.

Source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java:67

    DbClient dbClient,
    Indexers indexers,
    IssueCountHistoryRepository issueCountHistoryRepository,
    MeasureHistoryRepository measureHistoryRepository) {
    this.dbClient = dbClient;
    this.indexers = indexers;
    this.issueCountHistoryRepository = issueCountHistoryRepository;
    this.measureHistoryRepository = measureHistoryRepository;
  }

  public void delete(DbSession dbSession, List<ProjectDto> projects) {
    for (ProjectDto project : projects) {
      deleteEntity(dbSession, project);
    }
  }

  public void deleteBranch(DbSession dbSession, BranchDto branch) {
    if (branch.isMain()) {
      throw new IllegalArgumentException("Only non-main branches can be deleted");
    }
    deleteHistoryForEntity(dbSession, branch.getUuid(), EntityType.PROJECT_BRANCH);
    dbClient.purgeDao().deleteBranch(dbSession, branch.getUuid());
    updateProjectNcloc(dbSession, branch.getProjectUuid());
    indexers.commitAndIndexBranches(dbSession, singletonList(branch), BranchEvent.DELETION);
  }

  private void updateProjectNcloc(DbSession dbSession, String projectUuid) {
    List<String> branchUuids = dbClient.branchDao().selectByProjectUuid(dbSession, projectUuid).stream()
      .map(BranchDto::getUuid)
      .toList();
    long maxncloc = dbClient.measureDao().findNclocOfBiggestBranch(dbSession, branchUuids);
    dbClient.projectDao().updateNcloc(dbSession, projectUuid, maxncloc);
  }

  public void deleteEntity(DbSession dbSession, EntityDto entity) {
    checkArgument(!entity.getQualifier().equals(ComponentQualifiers.SUBVIEW), "Qualifier can't be subview");
    EntityType entityType = getEntityTypeForQualifier(entity.getQualifier());

View on GitHub (pinned to 184c821202)

Solutions

  1. Delete the whole project instead (api/projects/delete) when you intend to remove the main branch
  2. Filter out main branches before iterating: skip branches where isMain()==true
  3. Rename/replace the main branch by making another branch main first, then delete the old one
  4. Update automation to use branch name checks ('master'/'main') as a pre-guard

Example fix

// before
branches.forEach(b -> cleanerService.deleteBranch(session, b));
// after
branches.stream().filter(b -> !b.isMain()).forEach(b -> cleanerService.deleteBranch(session, b));
Defensive patterns

Strategy: validation

Validate before calling

// list branches and filter main ones before deleting
const branches = await api.branches.list({ project: key });
const deletable = branches.filter(b => !b.isMain);
if (deletable.length === 0) throw new Error('only main branch exists; delete the project instead');

Try / catch

try {
  await api.branches.delete({ project: key, branch: name });
} catch (e) {
  if (e.status === 400 && /Only non-main branches can be deleted/.test(e.message)) {
    return api.projects.delete({ project: key }); // fall back to project deletion
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling deleteBranch (backing api/projects/delete_branch) with the branch DTO whose isMain flag is true.

Common situations: Client code deleting 'master'/'main' via the branch-delete endpoint; automation iterating all branches without filtering isMain; stale clients that predate main-branch protection.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/7ee4270e672a944a. Report an issue: GitHub.