{"record":{"id":"7ee4270e672a944a","repo":"SonarSource/sonarqube","slug":"only-non-main-branches-can-be-deleted","errorCode":null,"errorMessage":"Only non-main branches can be deleted","messagePattern":"Only non-main branches can be deleted","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java","lineNumber":67,"sourceCode":"    DbClient dbClient,\n    Indexers indexers,\n    IssueCountHistoryRepository issueCountHistoryRepository,\n    MeasureHistoryRepository measureHistoryRepository) {\n    this.dbClient = dbClient;\n    this.indexers = indexers;\n    this.issueCountHistoryRepository = issueCountHistoryRepository;\n    this.measureHistoryRepository = measureHistoryRepository;\n  }\n\n  public void delete(DbSession dbSession, List<ProjectDto> projects) {\n    for (ProjectDto project : projects) {\n      deleteEntity(dbSession, project);\n    }\n  }\n\n  public void deleteBranch(DbSession dbSession, BranchDto branch) {\n    if (branch.isMain()) {\n      throw new IllegalArgumentException(\"Only non-main branches can be deleted\");\n    }\n    deleteHistoryForEntity(dbSession, branch.getUuid(), EntityType.PROJECT_BRANCH);\n    dbClient.purgeDao().deleteBranch(dbSession, branch.getUuid());\n    updateProjectNcloc(dbSession, branch.getProjectUuid());\n    indexers.commitAndIndexBranches(dbSession, singletonList(branch), BranchEvent.DELETION);\n  }\n\n  private void updateProjectNcloc(DbSession dbSession, String projectUuid) {\n    List<String> branchUuids = dbClient.branchDao().selectByProjectUuid(dbSession, projectUuid).stream()\n      .map(BranchDto::getUuid)\n      .toList();\n    long maxncloc = dbClient.measureDao().findNclocOfBiggestBranch(dbSession, branchUuids);\n    dbClient.projectDao().updateNcloc(dbSession, projectUuid, maxncloc);\n  }\n\n  public void deleteEntity(DbSession dbSession, EntityDto entity) {\n    checkArgument(!entity.getQualifier().equals(ComponentQualifiers.SUBVIEW), \"Qualifier can't be subview\");\n    EntityType entityType = getEntityTypeForQualifier(entity.getQualifier());","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java#L49-L85","documentation":"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.","triggerScenarios":"Calling deleteBranch (backing api/projects/delete_branch) with the branch DTO whose isMain flag is true.","commonSituations":"Client code deleting 'master'/'main' via the branch-delete endpoint; automation iterating all branches without filtering isMain; stale clients that predate main-branch protection.","solutions":["Delete the whole project instead (api/projects/delete) when you intend to remove the main branch","Filter out main branches before iterating: skip branches where isMain()==true","Rename/replace the main branch by making another branch main first, then delete the old one","Update automation to use branch name checks ('master'/'main') as a pre-guard"],"exampleFix":"// before\nbranches.forEach(b -> cleanerService.deleteBranch(session, b));\n// after\nbranches.stream().filter(b -> !b.isMain()).forEach(b -> cleanerService.deleteBranch(session, b));","handlingStrategy":"validation","validationCode":"// list branches and filter main ones before deleting\nconst branches = await api.branches.list({ project: key });\nconst deletable = branches.filter(b => !b.isMain);\nif (deletable.length === 0) throw new Error('only main branch exists; delete the project instead');","typeGuard":null,"tryCatchPattern":"try {\n  await api.branches.delete({ project: key, branch: name });\n} catch (e) {\n  if (e.status === 400 && /Only non-main branches can be deleted/.test(e.message)) {\n    return api.projects.delete({ project: key }); // fall back to project deletion\n  }\n  throw e;\n}","preventionTips":["Check b.isMain (or isMain()) before any branch deletion call","Use api/projects/delete for main-branch removal","Exclude 'master'/'main' names as an extra guard in automation scripts","Log skipped main branches during bulk cleanup"],"tags":["java","projects","branches"],"backgroundTag":"unsupported-operation","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}