{"record":{"id":"0642094466c5ac40","repo":"theonedev/onedev","slug":"code-read-permission-required-for-project-s","errorCode":null,"errorMessage":"Code read permission required for project: %s","messagePattern":"Code read permission required for project: (.+?)","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/mail/DefaultMailService.java","lineNumber":552,"sourceCode":"\t\t\t\t\t\t\t\t\t\t\tPullRequestWatch watch = pullRequestWatchService.find(involvedPullRequest, fromUser);\n\t\t\t\t\t\t\t\t\t\t\tif (watch != null) \n\t\t\t\t\t\t\t\t\t\t\t\twatch.setWatching(false);\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tString subject = \"Unsubscribed successfully from pull request \" + involvedPullRequest.getReference().toString(null);\n\n\t\t\t\t\t\t\t\t\t\t\tString template = StringUtils.join(settingService.getEmailTemplates().getPullRequestNotificationUnsubscribed(), \"\\n\");\n\t\t\t\t\t\t\t\t\t\t\tMap<String, Object> bindings = new HashMap<>();\n\t\t\t\t\t\t\t\t\t\t\tbindings.put(\"pullRequest\", involvedPullRequest);\n\t\t\t\t\t\t\t\t\t\t\tString htmlBody = EmailTemplates.evalTemplate(true, template, bindings);\n\t\t\t\t\t\t\t\t\t\t\tString textBody = EmailTemplates.evalTemplate(false, template, bindings);\n\t\t\t\t\t\t\t\t\t\t\tvar threadingReferences = getThreadingReferences(UUID.randomUUID().toString(), getMessageId(message));\n\t\t\t\t\t\t\t\t\t\t\tsendMailAsync(newArrayList(fromInternetAddress.getAddress()), newArrayList(), newArrayList(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tsubject, htmlBody, textBody, null, null, threadingReferences);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tif (!SecurityUtils.canReadCode(involvedPullRequest.getProject())) {\n\t\t\t\t\t\t\t\t\t\t\t\taddComment(involvedPullRequest, message, fromInternetAddress, fromUser, receiverInternetAddresses);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tthrow new UnauthorizedException(\"Code read permission required for project: %s\" \n\t\t\t\t\t\t\t\t\t\t\t\t\t\t+ involvedPullRequest.getProject().getPath());\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tthrow new ExplicitException(\"No account found with verified email address: \" + fromInternetAddress.getAddress());\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tthrow new ExplicitException(\"Invalid recipient address: \" + parsedReceiverAddress);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlogger.warn(\"Ignored recipient '\" + parsedReceiverAddress + \"' as issue or pull request is processed\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!receiverInternetAddress.equals(fromInternetAddress)) {\n\t\t\t\t\t\tinvolvedInternetAddresses.add(receiverInternetAddress);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (involvedIssue != null) {","sourceCodeStart":534,"sourceCodeEnd":570,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/mail/DefaultMailService.java#L534-L570","documentation":"When a recognized user without code-read permission on the pull request's project emails a pull request subaddress, an UnauthorizedException 'Code read permission required for project: <project path>' is thrown — PR email interaction is restricted to users who can read the project's code. (Note the source logic: addComment runs when the user CANNOT read code, and the exception fires otherwise, which reads inverted — verify intent before changing behavior.)","triggerScenarios":"Email to pullrequest~<id>~comment from a user account whose permissions on the PR's project fail SecurityUtils.canReadCode.","commonSituations":"Replying to a PR notification email after role changes removed code access; emailing a PR in a project the user has only issue-level access to.","solutions":["Grant the user code read permission on the pull request's project.","Comment on the PR through the OneDev web UI with a permitted account.","Review the source condition at DefaultMailService.java:552 — the exception path fires when canReadCode is true, so if the intent was the opposite, fix the inverted condition in code."],"exampleFix":"// before\nif (!SecurityUtils.canReadCode(involvedPullRequest.getProject())) {\n    addComment(...);\n} else {\n    throw new UnauthorizedException(\"Code read permission required for project: %s\" + involvedPullRequest.getProject().getPath());\n}\n// after\nif (SecurityUtils.canReadCode(involvedPullRequest.getProject())) {\n    addComment(...);\n} else {\n    throw new UnauthorizedException(\"Code read permission required for project: \" + involvedPullRequest.getProject().getPath());\n}","handlingStrategy":"try-catch","validationCode":"if (!SecurityUtils.canReadCode(pr.getProject())) throw new UnauthorizedException(\"Code read permission required for project: \" + pr.getProject().getPath());","typeGuard":null,"tryCatchPattern":"try { addComment(pr, message, from, user, receivers); } catch (UnauthorizedException e) { log.warn(\"PR email comment rejected: {}\", e.getMessage()); }","preventionTips":["Ensure code read permission before emailing PRs","Review the possibly-inverted condition at DefaultMailService.java:552","Prefer web UI for PR discussions when access is uncertain"],"tags":["email","authorization","permissions","pull-request"],"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"}