{"record":{"id":"ff78c8621894a2d4","repo":"theonedev/onedev","slug":"no-permission-to-update-issue-fields","errorCode":null,"errorMessage":"No permission to update issue fields","messagePattern":"No permission to update issue fields","errorType":"http","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/ai/TodResource.java","lineNumber":649,"sourceCode":"\n        @SuppressWarnings(\"unchecked\")\n        List<String> iterationNames = (List<String>) data.remove(\"iterations\");\n        if (iterationNames != null) {\n            if (!SecurityUtils.canScheduleIssues(subject, issue.getProject()))\n                throw new UnauthorizedException(\"Issue schedule permission required to set iterations\");\n            var iterations = new ArrayList<Iteration>();\n            for (var iterationName : iterationNames) {\n                var iteration = iterationService.findInHierarchy(issue.getProject(), iterationName);\n                if (iteration == null)\n                    throw new NotFoundException(\"Iteration '\" + iterationName + \"' not found\");\n                iterations.add(iteration);\n            }\n            issueChangeService.changeIterations(user, issue, iterations);\n        }\n\n        if (!data.isEmpty()) {\n            if (!SecurityUtils.canEditIssueFields(subject, issue)) \n                throw new UnauthorizedException(\"No permission to update issue fields\");\n\n            issueChangeService.changeFields(user, issue, FieldUtils.getFieldValues(subject, issue.getProject(), data));\n        }\n\n        return IssueHelper.getDetail(currentProject, issue);\n    }\n\n    @Path(\"/change-issue-state\")\n    @POST\n    public Map<String, Object> changeIssueState(\n                @QueryParam(\"currentProject\") @NotNull String currentProjectPath, \n                @QueryParam(\"reference\") @NotNull String issueReference, \n                @NotNull Map<String, Serializable> data) {\n        var subject = SecurityUtils.getSubject();\n        var user = SecurityUtils.getUser(subject);\n        if (user == null)\n            throw new UnauthenticatedException();\n","sourceCodeStart":631,"sourceCodeEnd":667,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/ai/TodResource.java#L631-L667","documentation":"Thrown as UnauthorizedException when editIssue still has remaining non-empty data fields after handling state/iterations etc., but the subject lacks the permission to edit issue fields on that issue (SecurityUtils.canEditIssueFields). OneDev refuses to apply arbitrary field changes from a user without field-edit rights.","triggerScenarios":"Calling the AI tod editIssue endpoint with any remaining keys in the data map (custom fields, etc.) while the authenticated user cannot edit issue fields for the issue (e.g., not issue author, reporter, editor, or project manager per project issue permission settings).","commonSituations":"Low-privileged user tries to edit custom fields; AI agent operates on behalf of a read-mostly user; project restricts field editing to specific roles.","solutions":["Grant the user permission to edit issue fields (e.g., 'Edit Issue' / appropriate role) on the project.","Perform the field update as a user with edit rights.","Remove the extra fields from the request if only state/iterations changes were intended."],"exampleFix":"// before\nMap<String, Serializable> data = Map.of(\"priority\", \"High\"); // user can't edit fields\n// after: use an account with field-edit permission or omit the field\nMap<String, Serializable> data = Map.of();","handlingStrategy":"try-catch","validationCode":"// only include data keys the user is allowed to edit\nconst editable = canEditIssueFields(user, issue);\nif (!editable && Object.keys(data).length > 0) {\n  throw new Error(\"User cannot edit issue fields; dropping field updates\");\n}","typeGuard":"function canEditFields(user, issue) {\n  return issue.permissions?.editFields === true;\n}","tryCatchPattern":"try {\n  await editIssue(project, ref, data);\n} catch (e) {\n  if (e.status === 401 || /No permission to update issue fields/.test(e.message)) {\n    // retry with a privileged identity or report to the caller\n  } else throw e;\n}","preventionTips":["Verify field-edit permission for the target issue before sending field data.","Keep AI agent identities with only the permissions they need, and check first.","Strip unauthorized keys from the payload instead of failing the whole call."],"tags":["authorization","permissions","issue-fields","onedev"],"backgroundTag":"permission-denied","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}