{"record":{"id":"2c3dd436db0588e3","repo":"theonedev/onedev","slug":"source-issue-already-linked-to-target-issue-via-sp","errorCode":null,"errorMessage":"Source issue already linked to target issue via specified link spec","messagePattern":"Source issue already linked to target issue via specified link spec","errorType":"validation","errorClass":"ValidationException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/model/IssueLink.java","lineNumber":91,"sourceCode":"\tpublic Issue getLinked(Issue issue) {\n\t\treturn issue.equals(source)?target:source;\n\t}\n\n\tpublic int getPosition() {\n\t\treturn position;\n\t}\n\n\tpublic void setPosition(int position) {\n\t\tthis.position = position;\n\t}\n\n\tpublic void validate() {\n\t\tif (getSource().equals(getTarget()))\n\t\t\tthrow new ValidationException(\"Cannot link to self\");\n\t\tif (getSpec().getOpposite() != null) {\n\t\t\tif (getSource().getTargetLinks().stream()\n\t\t\t\t\t.anyMatch(it -> it.getSpec().equals(getSpec()) && it.getTarget().equals(getTarget())))\n\t\t\t\tthrow new ValidationException(\"Source issue already linked to target issue via specified link spec\");\n\t\t\tif (!getSpec().isMultiple()\n\t\t\t\t\t&& getSource().getTargetLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))\n\t\t\t\tthrow new ValidationException(\n\t\t\t\t\t\t\"Link spec is not multiple and the source issue is already linked to another issue via this link spec\");\n\t\t\tif (!getSpec().getParsedIssueQuery(getSource().getProject()).matches(getTarget()))\n\t\t\t\tthrow new ValidationException(\"Link spec not allowed to link to the target issue\");\n\t\t\tif (!getSpec().getOpposite().isMultiple()\n\t\t\t\t\t&& getTarget().getSourceLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))\n\t\t\t\tthrow new ValidationException(\n\t\t\t\t\t\t\"Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec\");\n\t\t\tif (!getSpec().getOpposite().getParsedIssueQuery(getSource().getProject())\n\t\t\t\t\t.matches(getSource()))\n\t\t\t\tthrow new ValidationException(\"Opposite side of link spec not allowed to link to the source issue\");\n\t\t} else {\n\t\t\tif (getSource().getLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())\n\t\t\t\t\t&& it.getLinked(getSource()).equals(getTarget())))\n\t\t\t\tthrow new ValidationException(\"Specified issues already linked via specified link spec\");\n\t\t\tif (!getSpec().isMultiple()","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/model/IssueLink.java#L73-L109","documentation":"IssueLink.validate also rejects duplicate or conflicting links when the spec has an opposite (bidirectional) definition. If the source issue already has a link via this spec to the same target, ValidationException 'Source issue already linked to target issue via specified link spec' is thrown.","triggerScenarios":"Calling createLink twice for the same source/target/spec pair; concurrent automation both creating the same link; retried import scripts re-running link creation.","commonSituations":"Idempotency issues in bulk import scripts; UI double-submit creating the link twice; automation that recreates links after issue edits without checking existing links.","solutions":["Check for an existing link with the same spec and target before creating (query source.getTargetLinks())","Make link creation idempotent in scripts by skipping existing pairs","Fix double-submit in UI/automation (disable button, dedupe requests)"],"exampleFix":"// before\nissueLinkService.createLink(source, target, spec);  // may duplicate\n// after\nboolean exists = source.getTargetLinks().stream().anyMatch(l -> l.getSpec().equals(spec) && l.getTarget().equals(target));\nif (!exists) issueLinkService.createLink(source, target, spec);","handlingStrategy":"validation","validationCode":"boolean exists = source.getTargetLinks().stream().anyMatch(l -> l.getSpec().equals(spec) && l.getTarget().equals(target));","typeGuard":null,"tryCatchPattern":"try { createLink(source, target, spec); } catch (ValidationException e) { /* treat as already-linked, continue */ }","preventionTips":["Make link creation idempotent","Check existing target links before creating","Prevent double-submit in UI/automation"],"tags":["issues","validation","links"],"backgroundTag":"invalid-state-transition","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}