theonedev/onedev · error · ExplicitException

Unexpected attachment url:

Error message

Unexpected attachment url: 

What it means

Thrown by the YouTrack attachment processor when an attachment URL returned by the YouTrack API does not start with "/api". The importer expects relative API attachment URLs so it can prefix the server base endpoint; an absolute URL or unexpected path means YouTrack's API shape changed or the value is malformed, so it fails fast.

Solutions

  1. Upgrade or downgrade the YouTrack importer plugin to match your YouTrack server version.
  2. Check YouTrack version — this plugin expects the REST API returning relative /api attachment URLs.
  3. If behind a proxy, ensure the API responses are not rewritten to absolute URLs.
  4. Inspect the attachment JSON to confirm the url field shape and report/mismatch-handling accordingly.

Example fix

// before: absolute url from YouTrack breaks check
"url": "https://youtrack.example.com/api/attachments/1-2"

// after: proxy config preserving relative API url
"url": "/api/attachments/1-2"
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check attachment urls before download
for (JsonNode att : attachments) {
    String url = att.path("url").asText(null);
    if (url != null && !url.startsWith("/api"))
        logger.warn("Skipping non-relative attachment url: {}", url);
}

Try / catch

try { processAttachments(...); } catch (ExplicitException e) { logger.error("Attachment url format issue: {}", e.getMessage()); }

Prevention

When it happens

Trigger: YouTrack attachment "url" attribute is an absolute URL (e.g. https://youtrack.example.com/api/attachments/123) or some other path instead of the expected "/api/attachments/..." relative form, e.g. when using a proxy that rewrites URLs or a newer/older YouTrack REST version.

Common situations: YouTrack behind a reverse proxy that emits absolute URLs; YouTrack version upgrade changing the attachments payload; custom YouTrack cloud instance returning full URLs.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java:394

				private String processAttachments(String issueUUID, String readableIssueId, @Nullable String markdown,
												  List<JsonNode> attachmentNodes, Set<String> tooLargeAttachments) {
					if (markdown == null)
						markdown = "";

					Map<String, String> unreferencedAttachments = new LinkedHashMap<>();

					long maxUploadFileSize = OneDev.getInstance(SettingService.class)
							.getPerformanceSetting().getMaxUploadFileSize() * 1L * 1024 * 1024;
					for (JsonNode attachmentNode : attachmentNodes) {
						String attachmentName = attachmentNode.get("name").asText(null);
						String attachmentUrl = attachmentNode.get("url").asText(null);
						long attachmentSize = attachmentNode.get("size").asLong(0);
						if (attachmentSize != 0 && attachmentName != null && attachmentUrl != null) {
							if (attachmentSize > maxUploadFileSize) {
								tooLargeAttachments.add(readableIssueId + ":" + attachmentName);
							} else {
								if (!attachmentUrl.startsWith("/api"))
									throw new ExplicitException("Unexpected attachment url: " + attachmentUrl);
								attachmentUrl = attachmentUrl.substring("/api".length());

								String endpoint = getApiEndpoint(attachmentUrl);
								WebTarget target = client.target(endpoint);
								Invocation.Builder builder = target.request();
								try (Response response = builder.get()) {
									String errorMessage = JerseyUtils.checkStatus(endpoint, response);
									if (errorMessage != null) {
										throw new ExplicitException(String.format(
												"Error downloading attachment (url: %s, error message: %s)",
												endpoint, errorMessage));
									}
									try (InputStream is = response.readEntity(InputStream.class)) {
										AttachmentService attachmentService = OneDev.getInstance(AttachmentService.class);
										String oneDevAttachmentName = attachmentService.saveAttachment(
												oneDevProject.getId(), issueUUID, attachmentName, is);
										String oneDevAttachmentUrl = oneDevProject.getAttachmentUrlPath(issueUUID, oneDevAttachmentName);
										if (markdown.contains("(" + attachmentName + ")")) {

View on GitHub (pinned to d44925c47c)