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.

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)

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.