{"record":{"id":"dbe41f290e85d20f","repo":"theonedev/onedev","slug":"unexpected-attachment-url","errorCode":null,"errorMessage":"Unexpected attachment url: ","messagePattern":"Unexpected attachment url: ","errorType":"exception","errorClass":"ExplicitException","httpStatus":null,"severity":"error","filePath":"server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java","lineNumber":394,"sourceCode":"\t\t\t\tprivate String processAttachments(String issueUUID, String readableIssueId, @Nullable String markdown,\n\t\t\t\t\t\t\t\t\t\t\t\t  List<JsonNode> attachmentNodes, Set<String> tooLargeAttachments) {\n\t\t\t\t\tif (markdown == null)\n\t\t\t\t\t\tmarkdown = \"\";\n\n\t\t\t\t\tMap<String, String> unreferencedAttachments = new LinkedHashMap<>();\n\n\t\t\t\t\tlong maxUploadFileSize = OneDev.getInstance(SettingService.class)\n\t\t\t\t\t\t\t.getPerformanceSetting().getMaxUploadFileSize() * 1L * 1024 * 1024;\n\t\t\t\t\tfor (JsonNode attachmentNode : attachmentNodes) {\n\t\t\t\t\t\tString attachmentName = attachmentNode.get(\"name\").asText(null);\n\t\t\t\t\t\tString attachmentUrl = attachmentNode.get(\"url\").asText(null);\n\t\t\t\t\t\tlong attachmentSize = attachmentNode.get(\"size\").asLong(0);\n\t\t\t\t\t\tif (attachmentSize != 0 && attachmentName != null && attachmentUrl != null) {\n\t\t\t\t\t\t\tif (attachmentSize > maxUploadFileSize) {\n\t\t\t\t\t\t\t\ttooLargeAttachments.add(readableIssueId + \":\" + attachmentName);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (!attachmentUrl.startsWith(\"/api\"))\n\t\t\t\t\t\t\t\t\tthrow new ExplicitException(\"Unexpected attachment url: \" + attachmentUrl);\n\t\t\t\t\t\t\t\tattachmentUrl = attachmentUrl.substring(\"/api\".length());\n\n\t\t\t\t\t\t\t\tString endpoint = getApiEndpoint(attachmentUrl);\n\t\t\t\t\t\t\t\tWebTarget target = client.target(endpoint);\n\t\t\t\t\t\t\t\tInvocation.Builder builder = target.request();\n\t\t\t\t\t\t\t\ttry (Response response = builder.get()) {\n\t\t\t\t\t\t\t\t\tString errorMessage = JerseyUtils.checkStatus(endpoint, response);\n\t\t\t\t\t\t\t\t\tif (errorMessage != null) {\n\t\t\t\t\t\t\t\t\t\tthrow new ExplicitException(String.format(\n\t\t\t\t\t\t\t\t\t\t\t\t\"Error downloading attachment (url: %s, error message: %s)\",\n\t\t\t\t\t\t\t\t\t\t\t\tendpoint, errorMessage));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttry (InputStream is = response.readEntity(InputStream.class)) {\n\t\t\t\t\t\t\t\t\t\tAttachmentService attachmentService = OneDev.getInstance(AttachmentService.class);\n\t\t\t\t\t\t\t\t\t\tString oneDevAttachmentName = attachmentService.saveAttachment(\n\t\t\t\t\t\t\t\t\t\t\t\toneDevProject.getId(), issueUUID, attachmentName, is);\n\t\t\t\t\t\t\t\t\t\tString oneDevAttachmentUrl = oneDevProject.getAttachmentUrlPath(issueUUID, oneDevAttachmentName);\n\t\t\t\t\t\t\t\t\t\tif (markdown.contains(\"(\" + attachmentName + \")\")) {","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-plugin/server-plugin-import-youtrack/src/main/java/io/onedev/server/plugin/imports/youtrack/ImportServer.java#L376-L412","documentation":"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.","triggerScenarios":"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.","commonSituations":"YouTrack behind a reverse proxy that emits absolute URLs; YouTrack version upgrade changing the attachments payload; custom YouTrack cloud instance returning full URLs.","solutions":["Upgrade or downgrade the YouTrack importer plugin to match your YouTrack server version.","Check YouTrack version — this plugin expects the REST API returning relative /api attachment URLs.","If behind a proxy, ensure the API responses are not rewritten to absolute URLs.","Inspect the attachment JSON to confirm the url field shape and report/mismatch-handling accordingly."],"exampleFix":"// before: absolute url from YouTrack breaks check\n\"url\": \"https://youtrack.example.com/api/attachments/1-2\"\n\n// after: proxy config preserving relative API url\n\"url\": \"/api/attachments/1-2\"","handlingStrategy":"try-catch","validationCode":"// sanity-check attachment urls before download\nfor (JsonNode att : attachments) {\n    String url = att.path(\"url\").asText(null);\n    if (url != null && !url.startsWith(\"/api\"))\n        logger.warn(\"Skipping non-relative attachment url: {}\", url);\n}","typeGuard":null,"tryCatchPattern":"try { processAttachments(...); } catch (ExplicitException e) { logger.error(\"Attachment url format issue: {}\", e.getMessage()); }","preventionTips":["Test the plugin against your YouTrack version before bulk import.","Ensure reverse proxies do not rewrite YouTrack API responses to absolute URLs.","Keep the importer plugin updated for your YouTrack REST version."],"tags":["youtrack","import","attachments","api-response"],"backgroundTag":"unexpected-api-response-shape","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"}