{"record":{"id":"fd700f9bd5b7f45d","repo":"theonedev/onedev","slug":"issue-schedule-permission-required-to-set-iteratio-fd700f","errorCode":null,"errorMessage":"Issue schedule permission required to set iterations","messagePattern":"Issue schedule permission required to set iterations","errorType":"http","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/ai/TodResource.java","lineNumber":636,"sourceCode":"            issueChangeService.changeConfidential(user, issue, confidential);\n        }\n\n        Integer ownEstimatedTime = (Integer) data.remove(\"ownEstimatedTime\");\n        if (ownEstimatedTime != null) {\n            if (!subscriptionService.isSubscriptionActive())\n                throw new NotAcceptableException(\"An active subscription is required for this feature\");\n            if (!issue.getProject().isTimeTracking())\n                throw new NotAcceptableException(\"Time tracking needs to be enabled for the project\");\n            if (!SecurityUtils.canScheduleIssues(subject, issue.getProject()))\n                throw new UnauthorizedException(\"Issue schedule permission required to set own estimated time\");\n            issueChangeService.changeOwnEstimatedTime(user, issue, ownEstimatedTime*60);\n        }\n\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);","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/ai/TodResource.java#L618-L654","documentation":"OneDev's AI tod resource throws UnauthorizedException when an agent attempts to set an issue's 'iterations' field via editIssue without the 'Schedule Issues' permission on the issue's project. The check runs via SecurityUtils.canScheduleIssues before any iteration lookup happens, so the request is rejected outright.","triggerScenarios":"Calling the AI tod editIssue endpoint (TodResource.editIssue) with an 'iterations' key in the data map while the authenticated subject lacks issue schedule permission on the target project.","commonSituations":"AI agent acting as a user who can edit issue fields but not manage schedules; project where only project owners/managers have Schedule Issues; permission recently revoked from the user.","solutions":["Grant the user (or their group) the 'Schedule Issues' permission in the project's or organization's role settings.","Use an authenticated identity that has scheduling rights on the project.","Remove the 'iterations' key from the request data and change iterations through the regular UI/API with a permitted account."],"exampleFix":"// before (client sends iterations as underprivileged user)\ndata.put(\"iterations\", List.of(\"Sprint 1\"));\n// after: authenticate as a user with Schedule Issues permission, or drop the key\nif (SecurityUtils.canScheduleIssues(subject, project)) {\n    data.put(\"iterations\", List.of(\"Sprint 1\"));\n}","handlingStrategy":"try-catch","validationCode":"// client-side check before calling editIssue with 'iterations'\nif (!userPermissions.includes(\"Schedule Issues\")) {\n    throw new Error(\"Skipping iterations update: schedule permission missing\");\n}","typeGuard":"function canSchedule(user, project) {\n  return Boolean(user?.projects?.[project]?.permissions?.includes(\"Schedule Issues\"));\n}","tryCatchPattern":"try {\n  await editIssue(project, ref, { iterations: [\"Sprint 1\"] });\n} catch (e) {\n  if (e.status === 401 || /schedule permission/i.test(e.message)) {\n    // fall back to a permitted identity or skip iteration changes\n  } else throw e;\n}","preventionTips":["Check the user's effective project permissions before planning iteration changes.","Keep AI agent identities in groups that include Schedule Issues when iteration management is intended.","Audit role settings after project permission changes."],"tags":["authorization","permissions","issues","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-14T05:17:10.506Z"}