theonedev/onedev · error · NotAcceptableException

Pull request already closed

Error message

Pull request already closed

What it means

discard throws this NotAcceptableException when the pull request is not open — it is already merged, discarded, or otherwise closed. A PR can only transition to DISCARDED from the OPEN state, so discarding a closed PR is rejected.

Source

Thrown at server-core/src/main/java/io/onedev/server/service/impl/DefaultPullRequestService.java:337

		PullRequestChange change = new PullRequestChange();
		change.setData(new PullRequestReopenData());
		change.setRequest(request);
		change.setUser(user);
		changeService.create(change, note);

		MergePreview mergePreview = request.checkMergePreview();
		if (mergePreview != null)
			updateMergePreviewRef(request);

		checkAsync(request, false, true);
	}

	@Transactional
	@Override
 	public void discard(User user, PullRequest request, String note) {
        if (!request.isOpen())
            throw new NotAcceptableException("Pull request already closed");

		request.setStatus(Status.DISCARDED);
		request.setCloseDate(new Date());

		PullRequestChange change = new PullRequestChange();
		change.setData(new PullRequestDiscardData());
		change.setRequest(request);
		change.setUser(user);
		changeService.create(change, note);

		pendingSuggestionApplyService.discard(null, request);
	}

	@Transactional
	@Override
	public void merge(User user, PullRequest request, @Nullable String commitMessage) {
        var errorMessage = request.checkMergeCondition();
        if (errorMessage != null)

View on GitHub (pinned to d44925c47c)

Solutions

  1. Check request.isOpen() before calling discard and skip if already closed
  2. Treat the PR as already handled — no action needed if it was merged or discarded
  3. Handle the NotAcceptableException and read the PR's current status to decide next steps

Example fix

// before
prService.discard(user, request, "no longer needed"); // 400 "Pull request already closed"
// after
if (request.isOpen())
    prService.discard(user, request, "no longer needed");
Defensive patterns

Strategy: validation

Validate before calling

boolean canDiscard = request.isOpen();

Try / catch

try {
    prService.discard(user, request, note);
} catch (NotAcceptableException e) {
    // PR already merged/discarded; no-op
}

Prevention

When it happens

Trigger: Calling DefaultPullRequestService.discard(user, request, note) when request.isOpen() is false — i.e. the PR status is MERGED, DISCARDED, or any non-open state. Also triggered via REST discard endpoint or checkAutoMerge's check path racing a concurrent close.

Common situations: Double-clicking / retrying a discard action; automation discarding PRs without checking state; a race where auto-merge merged the PR just before the discard call; REST scripts replaying old requests.

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 theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/2ecd1be198929cac. Report an issue: GitHub.