theonedev/onedev · error · io.onedev.server.exception.NotAcceptableException

Reason is required

Error message

Reason is required

What it means

When submitting a job through TodResource, a human-readable 'reason' string is mandatory; it is recorded as the audit context passed to jobService.submit (and resubmit). A null/blank reason causes NotAcceptableException before any build is created.

Source

Thrown at server-core/src/main/java/io/onedev/server/ai/TodResource.java:1735

            params = new HashMap<String, List<String>>();
            List<String> paramPairs = (List<String>) paramData;
            if (paramPairs != null) {
                for (var paramPair: paramPairs) {
                    var paramName = trimToNull(StringUtils.substringBefore(paramPair, "="));
                    var paramValue = trimToNull(StringUtils.substringAfter(paramPair, "="));
                    if (paramName != null && paramValue != null)
                        params.computeIfAbsent(paramName, k -> new ArrayList<>()).add(paramValue);
                }
            }
        } else if (paramData instanceof Map) {
            params = (Map<String, List<String>>) paramData;
        } else {
            params = new HashMap<String, List<String>>();
        }

        var reason = trimToNull((String)data.get("reason"));
        if (reason == null)
            throw new NotAcceptableException("Reason is required");
            
        var build = jobService.submit(user, project, ObjectId.fromString(commitHash), jobName, 
            params, refName, null, null, reason);
        if (build.isFinished())
            jobService.resubmit(user, build, reason);

        var summary = BuildHelper.getSummary(project, build);
        summary.put("id", build.getId());
        return summary;
    }

    @Path("/get-clone-roots")
    @GET
    public Map<String, String> getCloneRoots() {
        if (SecurityUtils.getUser() == null)
            throw new UnauthenticatedException();

        var cloneRoots = new HashMap<String, String>();

View on GitHub (pinned to d44925c47c)

Solutions

  1. Set data.put("reason", "<why this build is being submitted>") before calling the endpoint.
  2. Generate a default reason programmatically if none is available (e.g. 'triggered by AI agent').
  3. Audit your tool-calling layer to always populate the reason field.

Example fix

// before
var data = Map.of("branch", "main", "jobName", "ci");
// after
var data = Map.of("branch", "main", "jobName", "ci", "reason", "manual rebuild requested by user");
Defensive patterns

Strategy: validation

Validate before calling

if (!(data.reason ?? "").trim()) throw new Error("reason is required for job submission");

Type guard

function hasReason(d) { return typeof d.reason === "string" && d.reason.trim().length > 0; }

Try / catch

try { submit(data); } catch (e) { if (e.status === 406 && e.message.includes("Reason is required")) { data.reason = "automated submission"; return submit(data); } throw e; }

Prevention

When it happens

Trigger: Calling the job-submit tool endpoint with a valid commitHash/branch/tag and params, but data.get("reason") null or empty.

Common situations: AI agents or scripts automating build submissions that treat 'reason' as optional metadata; refactors of tool payloads that drop the reason field.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/f948b36c92091db3. Report an issue: GitHub.