{"record":{"id":"428d341926ea240a","repo":"gastownhall/beads","slug":"add-link-s-to-d-w","errorCode":null,"errorMessage":"add link %s to %d: %w","messagePattern":"add link (.+?) to (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ado/links.go","lineNumber":329,"sourceCode":"\tfor _, idx := range removeIndices {\n\t\tif err := r.Client.RemoveWorkItemLink(ctx, workItemID, idx); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"remove relation %d: %w\", idx, err))\n\t\t}\n\t}\n\n\t// Find relations to add (in desired but not current).\n\tfor key, dep := range desired {\n\t\tif currentSet[key] {\n\t\t\tcontinue\n\t\t}\n\t\ttargetURL := r.buildWorkItemURL(key.TargetID)\n\t\trel := beadsDepToADORel(dep.Type)\n\t\tcomment := \"\"\n\t\tif dep.Type == \"discovered-from\" {\n\t\t\tcomment = discoveredFromComment\n\t\t}\n\t\tif err := r.Client.AddWorkItemLink(ctx, workItemID, targetURL, rel, comment); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"add link %s to %d: %w\", rel, key.TargetID, err))\n\t\t}\n\t}\n\n\treturn errs\n}\n","sourceCodeStart":311,"sourceCodeEnd":335,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/ado/links.go#L311-L335","documentation":"During PushLinks, links desired in beads but missing in Azure DevOps are added via AddWorkItemLink with the mapped relation type (rel) and target work item ID. Failures are accumulated and wrapped as 'add link <relationType> to <targetID>'. Like removals, one bad add does not abort the rest of the sync.","triggerScenarios":"AddWorkItemLink returns an error: target work item ID (key.TargetID) does not exist in ADO (deleted/not-yet-mirrored), PAT lacks write permission, the relation type is invalid for the work item type, a duplicate/self link is rejected, or a network failure occurs.","commonSituations":"Beads contains a dependency whose counterpart bead has never been pushed to ADO (targetID has no ADO work item); blocked-by relations on work item types that disallow them; expired PAT; the dependency target was deleted in ADO.","solutions":["Check the wrapped inner error: 404 means the target work item doesn't exist — push/mirror the target bead to ADO first, then resync.","Verify PAT permissions include Work Items Write if 401/403.","Confirm beadsDepToADORel maps the dependency type to a relation valid in your ADO process (e.g. System.LinkTypes.Dependency-Forward).","Retry after transient network errors; additions are re-attempted on the next sync since current/desired sets are recomputed."],"exampleFix":"// before: adding links whose targets may not exist in ADO\nif err := r.Client.AddWorkItemLink(ctx, workItemID, targetURL, rel, comment); err != nil {\n    errs = append(errs, fmt.Errorf(\"add link %s to %d: %w\", rel, key.TargetID, err))\n}\n// after: verify target exists first\nif err := r.Client.AddWorkItemLink(ctx, workItemID, targetURL, rel, comment); err != nil {\n    var apiErr *APIError\n    if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {\n        errs = append(errs, fmt.Errorf(\"add link %s to %d: target work item missing in ADO, push it first\", rel, key.TargetID))\n        continue\n    }\n    errs = append(errs, fmt.Errorf(\"add link %s to %d: %w\", rel, key.TargetID, err))\n}","handlingStrategy":"try-catch","validationCode":"// Verify the target work item exists before adding the link\n_, resp, err := client.GetWorkItem(ctx, key.TargetID, nil)\nif err != nil || resp == nil || resp.StatusCode == http.StatusNotFound {\n    return fmt.Errorf(\"target work item %d missing in ADO; push it first\", key.TargetID)\n}","typeGuard":"func isMissingTargetErr(err error) bool {\n    var apiErr *APIError\n    return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound\n}","tryCatchPattern":"if err := r.Client.AddWorkItemLink(ctx, workItemID, targetURL, rel, comment); err != nil {\n    var apiErr *APIError\n    if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {\n        log.Printf(\"defer link %s->%d: target not yet in ADO\", rel, key.TargetID)\n        continue\n    }\n    errs = append(errs, fmt.Errorf(\"add link %s to %d: %w\", rel, key.TargetID, err))\n}","preventionTips":["Push/mirror all dependency targets to ADO before syncing links.","Grant the PAT Work Items Write scope.","Confirm relation types are valid for your ADO process template.","Re-run sync after fixing targets — adds are retried because desired/current sets are recomputed."],"tags":["azure-devops","link-sync","http-error","dependencies"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}