{"record":{"id":"d135e78585d90b2d","repo":"theonedev/onedev","slug":"not-authorized","errorCode":null,"errorMessage":"Not authorized","messagePattern":"Not authorized","errorType":"http","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/ai/TodResource.java","lineNumber":1503,"sourceCode":"        return PullRequestHelper.getDetail(currentProject, pullRequest);        \n    }\n\n    @Path(\"/merge-pull-request\")\n    @Consumes(MediaType.TEXT_PLAIN)\n    @POST\n    public Map<String, Object> mergePullRequest(\n                @QueryParam(\"currentProject\") @NotNull String currentProjectPath,\n                @QueryParam(\"reference\") @NotNull String pullRequestReference,\n                String commitMessage) {\n        var user = SecurityUtils.getUser();\n        if (user == null)\n            throw new UnauthenticatedException();\n\n        var currentProject = getProject(currentProjectPath);\n        var pullRequest = getPullRequest(currentProject, pullRequestReference);\n\n        if (!SecurityUtils.canWriteCode(user.asSubject(), pullRequest.getProject()))\n            throw new UnauthorizedException();\n\n        commitMessage = trimToNull(commitMessage);\n\n        pullRequestService.merge(user, pullRequest, commitMessage);\n\n        return PullRequestHelper.getDetail(currentProject, pullRequest);        \n    }\n\n    @Path(\"/discard-pull-request\")\n    @Consumes(MediaType.TEXT_PLAIN)\n    @POST\n    public Map<String, Object> discardPullRequest(\n                @QueryParam(\"currentProject\") @NotNull String currentProjectPath,\n                @QueryParam(\"reference\") @NotNull String pullRequestReference,\n                String comment) {\n        var user = SecurityUtils.getUser();\n        if (user == null)\n            throw new UnauthenticatedException();","sourceCodeStart":1485,"sourceCodeEnd":1521,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/ai/TodResource.java#L1485-L1521","documentation":"The mergePullRequest endpoint checks SecurityUtils.canWriteCode(user.asSubject(), pullRequest.getProject()); if the authenticated user lacks code write permission on the PR's project, an UnauthorizedException ('Not authorized') is thrown before the merge is performed.","triggerScenarios":"Calling POST /merge-pull-request as an authenticated user whose effective permissions on the project do not include code write (e.g. read-only role, guest access, or the PR belongs to a project the user cannot push to).","commonSituations":"Bot/API token scoped to a role without write access; user moved to a read-only group; PR lives in a different project than assumed and permission is checked against pullRequest.getProject().","solutions":["Grant the user or token's role code write permission on the PR's project in OneDev security settings.","Use a token/account that has write access to the target project.","Verify the correct project — permission is evaluated against the PR's own project, not currentProject."],"exampleFix":"// before\n// token user has only 'Read' role on project\nmergePullRequest(ref)\n// after\n// Admin: Project -> Security -> give role 'Code Write', or use an account with write access\nmergePullRequest(ref)","handlingStrategy":"validation","validationCode":"const me = await getCurrentUser()\nif (!canWriteCode(me, pr.project)) throw new Error(`user ${me.name} lacks code write permission on ${pr.project}`)","typeGuard":"function canMerge(user, project) { return Boolean(user?.permissions?.[project]?.includes('WRITE_CODE')) }","tryCatchPattern":"try { await mergePullRequest(ref) }\ncatch (e) { if (/not authorized|unauthorized/i.test(e.message)) { throw new Error(`Grant code write permission on the PR's project before merging: ${e.message}`) } else throw e }","preventionTips":["Verify the account/token role has Code Write on the PR's project","Remember permission is checked against pullRequest.getProject(), not the currentProject param","Use a dedicated bot account with the required role for merges"],"tags":["auth","permission","pull-request","merge"],"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"}