{"record":{"id":"94a53afaaa8fd7d7","repo":"theonedev/onedev","slug":"attachment-not-found","errorCode":null,"errorMessage":"Attachment not found: ","messagePattern":"Attachment not found: ","errorType":"exception","errorClass":"FileNotFoundException","httpStatus":404,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/attachment/DefaultAttachmentService.java","lineNumber":635,"sourceCode":"\t\t\t} else {\n\t\t\t\tif (!attachmentDir.getParentFile().getName().equals(TEMP))\n\t\t\t\t\tprojectService.directoryModified(projectId, attachmentDir);\n\t\t\t\treturn file.getName();\n\t\t\t}\n\t\t});\n\t}\n\t\n\t@Override\n\tpublic String getAttachmentLockName(Long projectId, String attachmentGroup) {\n\t\treturn \"attachment:\" + projectId + \":\" + attachmentGroup;\n\t}\n\n\t@Override\n\tpublic FileInfo getAttachmentInfo(Long projectId, String attachmentGroup, String attachment) {\n\t\treturn projectService.runOnActiveServer(projectId, () -> read(getAttachmentLockName(projectId, attachmentGroup), () -> {\n\t\t\tFile attachmentFile = new File(getAttachmentGroupDir(projectId, attachmentGroup), attachment);\n\t\t\tif (!attachmentFile.exists())\n\t\t\t\tthrow new FileNotFoundException(\"Attachment not found: \" + attachment);\n\t\t\treturn new FileInfo(attachment, attachmentFile.lastModified(), \n\t\t\t\t\tattachmentFile.length(), null);\n\t\t}));\n\t}\n\n\t@Override\n\tpublic void deleteAttachment(Long projectId, String attachmentGroup, String attachment) {\n\t\tprojectService.runOnActiveServer(projectId, () -> write(getAttachmentLockName(projectId, attachmentGroup), () -> {\n\t\t\tvar attachmentGroupDir = getAttachmentGroupDir(projectId, attachmentGroup);\n\t\t\tFile attachmentFile = new File(attachmentGroupDir, attachment);\n\t\t\tif (attachmentFile.exists()) {\n\t\t\t\tFileUtils.deleteFile(attachmentFile);\n\t\t\t\tprojectService.directoryModified(projectId, attachmentGroupDir);\n\t\t\t}\n\t\t\treturn null;\n\t\t}));\n\t}\n","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/attachment/DefaultAttachmentService.java#L617-L653","documentation":"DefaultAttachmentService.getAttachmentInfo reads an attachment file under an attachment group directory for a project. If the file does not exist on the active server's attachment storage, a java.io.FileNotFoundException with message 'Attachment not found: <name>' is thrown. The read is performed under the project's attachment lock to avoid concurrent modification.","triggerScenarios":"Calling getAttachmentInfo(projectId, attachmentGroup, attachment) with an attachment name that was never uploaded, was already deleted (e.g. via deleteAttachments or project cleanup), or with a misspelled attachment name. Also occurs when referencing attachments in CI job reports/artifacts that were never produced.","commonSituations":"CI job step or report referencing an attachment before it is uploaded; attachments lost when moving a project between servers because attachment files live outside git and are not migrated; typos in attachment file names in build spec or UI code; attachment purged by retention cleanup while still referenced.","solutions":["Verify the attachment exists by calling getAttachments(projectId, attachmentGroup) or listAttachmentNames before requesting its info","Check the exact attachment file name (case-sensitive) against what was passed to uploadAttachment","If attachments disappeared after a server move, copy the attachments storage directory (project attachments live outside the git repo) to the new server","Catch java.io.FileNotFoundException around getAttachmentInfo and treat it as a missing-resource condition rather than a crash"],"exampleFix":"// before\nFileInfo info = attachmentService.getAttachmentInfo(projectId, group, \"report.html\");\n// after\nif (!attachmentService.getAttachments(projectId, group).contains(\"report.html\")) {\n    throw new ExplicitException(\"Attachment report.html has not been uploaded yet\");\n}\nFileInfo info = attachmentService.getAttachmentInfo(projectId, group, \"report.html\");","handlingStrategy":"try-catch","validationCode":"if (!attachmentService.getAttachments(projectId, attachmentGroup).contains(attachment)) {\n    throw new ExplicitException(\"Attachment not uploaded: \" + attachment);\n}","typeGuard":null,"tryCatchPattern":"try {\n    FileInfo info = attachmentService.getAttachmentInfo(projectId, group, attachment);\n} catch (FileNotFoundException e) {\n    // treat as missing resource: skip, re-upload, or report\n}","preventionTips":["List available attachments before accessing one","Upload attachments before steps that consume them","Keep attachment storage backed up and copied during server migrations","Use consistent, programmatic attachment names instead of hand-typed ones"],"tags":["attachments","file-not-found","ci"],"backgroundTag":"file-not-found","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"}