theonedev/onedev · error · FileNotFoundException

Attachment not found:

Error message

Attachment not found: 

What it means

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.

Solutions

  1. Verify the attachment exists by calling getAttachments(projectId, attachmentGroup) or listAttachmentNames before requesting its info
  2. Check the exact attachment file name (case-sensitive) against what was passed to uploadAttachment
  3. If attachments disappeared after a server move, copy the attachments storage directory (project attachments live outside the git repo) to the new server
  4. Catch java.io.FileNotFoundException around getAttachmentInfo and treat it as a missing-resource condition rather than a crash

Example fix

// before
FileInfo info = attachmentService.getAttachmentInfo(projectId, group, "report.html");
// after
if (!attachmentService.getAttachments(projectId, group).contains("report.html")) {
    throw new ExplicitException("Attachment report.html has not been uploaded yet");
}
FileInfo info = attachmentService.getAttachmentInfo(projectId, group, "report.html");
Defensive patterns

Strategy: try-catch

Validate before calling

if (!attachmentService.getAttachments(projectId, attachmentGroup).contains(attachment)) {
    throw new ExplicitException("Attachment not uploaded: " + attachment);
}

Try / catch

try {
    FileInfo info = attachmentService.getAttachmentInfo(projectId, group, attachment);
} catch (FileNotFoundException e) {
    // treat as missing resource: skip, re-upload, or report
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/94a53afaaa8fd7d7. Report an issue: GitHub.

Appendix: source

Thrown at server-core/src/main/java/io/onedev/server/attachment/DefaultAttachmentService.java:635

			} else {
				if (!attachmentDir.getParentFile().getName().equals(TEMP))
					projectService.directoryModified(projectId, attachmentDir);
				return file.getName();
			}
		});
	}
	
	@Override
	public String getAttachmentLockName(Long projectId, String attachmentGroup) {
		return "attachment:" + projectId + ":" + attachmentGroup;
	}

	@Override
	public FileInfo getAttachmentInfo(Long projectId, String attachmentGroup, String attachment) {
		return projectService.runOnActiveServer(projectId, () -> read(getAttachmentLockName(projectId, attachmentGroup), () -> {
			File attachmentFile = new File(getAttachmentGroupDir(projectId, attachmentGroup), attachment);
			if (!attachmentFile.exists())
				throw new FileNotFoundException("Attachment not found: " + attachment);
			return new FileInfo(attachment, attachmentFile.lastModified(), 
					attachmentFile.length(), null);
		}));
	}

	@Override
	public void deleteAttachment(Long projectId, String attachmentGroup, String attachment) {
		projectService.runOnActiveServer(projectId, () -> write(getAttachmentLockName(projectId, attachmentGroup), () -> {
			var attachmentGroupDir = getAttachmentGroupDir(projectId, attachmentGroup);
			File attachmentFile = new File(attachmentGroupDir, attachment);
			if (attachmentFile.exists()) {
				FileUtils.deleteFile(attachmentFile);
				projectService.directoryModified(projectId, attachmentGroupDir);
			}
			return null;
		}));
	}

View on GitHub (pinned to d44925c47c)