SonarSource/sonarqube · error · IllegalArgumentException

Cannot change the assignee of this hotspot given its…

Error message

Cannot change the assignee of this hotspot given its current status and resolution

What it means

Security hotspot assignees can only be changed while the hotspot is still 'in review'. AssignAction.checkHotspotStatusAndResolution throws this IllegalArgumentException when the hotspot's status/resolution indicates it is no longer in TO_REVIEW state (and not merely ACKNOWLEDGED), so changing the assignee is not allowed.

Solutions

  1. Re-fetch the hotspot (api/hotspots/show) and confirm its status is TO_REVIEW before assigning.
  2. If the hotspot was already reviewed, do not reassign; it may need to be reopened via code change/re-analysis instead.
  3. Handle the race in automation: catch the 400 error and re-check current state before retrying.

Example fix

// before
// blindly assign without checking state
ws.post("api/hotspots/assign", {hotspot: key, assignee: login});
// after
const hotspot = await ws.get("api/hotspots/show", {hotspot: key});
if (hotspot.status === "TO_REVIEW") {
  await ws.post("api/hotspots/assign", {hotspot: key, assignee: login});
}
Defensive patterns

Strategy: try-catch

Validate before calling

const hotspot = await ws.get("api/hotspots/show", {hotspot: key});
if (hotspot.status !== "TO_REVIEW" && hotspot.resolution !== "ACKNOWLEDGED") {
  throw new Error("Hotspot no longer in review; cannot reassign");
}

Try / catch

try {
  await ws.post("api/hotspots/assign", {hotspot: key, assignee: login});
} catch (err) {
  if (err.message.includes("Cannot change the assignee of this hotspot")) {
    const cur = await ws.get("api/hotspots/show", {hotspot: key});
    log.warn("Hotspot state changed to {} by someone else", cur.status); // re-check and skip/reopen
  } else throw err;
}

Prevention

When it happens

Trigger: Calling api/hotspots/assign on a hotspot whose status is not 'TO_REVIEW' and whose resolution is not 'ACKNOWLEDGED' — i.e. it was already marked FIXED or SAFE by a reviewer, or closed by analysis changes.

Common situations: Two users editing the same hotspot concurrently (one closes it, the other then tries to reassign); automation reassigning stale hotspot keys after the underlying code changed; retrying an old assign request after the hotspot was reviewed.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java:145

      if (assignee != null) {
        checkAssigneeProjectPermission(dbSession, assignee, hotspotDto.getProjectUuid());
      }

      if (issueFieldsSetter.assign(defaultIssue, assignee, context)) {
        issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, hotspotDto, defaultIssue, context);

        BranchDto branch = issueUpdater.getBranch(dbSession, defaultIssue);
        if (BRANCH.equals(branch.getBranchType())) {
          HotspotChangedEvent hotspotChangedEvent = buildEventData(defaultIssue, assignee, hotspotDto.getFilePath());
          hotspotChangeEventService.distributeHotspotChangedEvent(branch.getProjectUuid(), hotspotChangedEvent);
        }
      }
    }
  }

  private static void checkHotspotStatusAndResolution(IssueDto hotspotDto) {
    if (!STATUS_TO_REVIEW.equals(hotspotDto.getStatus()) && !RESOLUTION_ACKNOWLEDGED.equals(hotspotDto.getResolution())) {
      throw new IllegalArgumentException("Cannot change the assignee of this hotspot given its current status and resolution");
    }
  }

  private UserDto getAssignee(DbSession dbSession, String assignee) {
    return checkFound(dbClient.userDao().selectActiveUserByLogin(dbSession, assignee), "Unknown user: %s", assignee);
  }

  private void checkAssigneeProjectPermission(DbSession dbSession, UserDto assignee, String issueBranchUuid) {
    ProjectDto project = checkFoundWithOptional(dbClient.projectDao().selectByBranchUuid(dbSession, issueBranchUuid),
      "Could not find branch for issue");

    if (project.isPrivate() && !hasProjectPermission(dbSession, assignee.getUuid(), project.getUuid())) {
      throw new IllegalArgumentException(String.format("Provided user with login '%s' does not have 'Browse' permission to project", assignee.getLogin()));
    }
  }

  private boolean hasProjectPermission(DbSession dbSession, String userUuid, String projectUuid) {
    return dbClient.authorizationDao().selectEntityPermissions(dbSession, projectUuid, userUuid).contains(ProjectPermission.USER.getKey());

View on GitHub (pinned to 184c821202)