{"record":{"id":"9b8f9fc3f3ad5a62","repo":"gastownhall/beads","slug":"link-sync-ado-d-v","errorCode":null,"errorMessage":"Link sync ADO #%d: %v","messagePattern":"Link sync ADO #(.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/bd/ado.go","lineNumber":857,"sourceCode":"\t\t\tcontinue\n\t\t}\n\n\t\t// Fetch current ADO work item to get existing relations.\n\t\tadoClient := at.ADOClient()\n\t\titems, ferr := adoClient.FetchWorkItems(ctx, []int{workItemID})\n\t\tif ferr != nil || len(items) == 0 {\n\t\t\tif warn != nil {\n\t\t\t\twarn(fmt.Sprintf(\"Failed to fetch ADO #%d for link sync: %v\", workItemID, ferr))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\terrs := resolver.PushLinks(ctx, workItemID, items[0].Relations, desired, managedTargets)\n\t\tfor _, e := range errs {\n\t\t\tmsg := fmt.Sprintf(\"Link sync ADO #%d: %v\", workItemID, e)\n\t\t\twarnings = append(warnings, msg)\n\t\t\tif warn != nil {\n\t\t\t\twarn(msg)\n\t\t\t}\n\t\t}\n\t\tlinkCount += len(desired) - len(errs)\n\t}\n\n\treturn linkCount, warnings\n}\n\n// buildADOPullHooks creates PullHooks for ADO-specific pull behavior.\n// When bootstrapMatch is true, incoming ADO items are matched against existing\n// local issues by external_ref, source_system, and heuristic before creating\n// duplicates. When noCreate is true, unmatched items are skipped entirely.\nfunc buildADOPullHooks(ctx context.Context, at *ado.Tracker, bootstrapMatch, noCreate bool, matchCount *int, warn func(string)) *tracker.PullHooks {\n\tprefix := \"bd\"\n\t// YAML config takes precedence — in shared-server mode the DB\n\t// may belong to a different project (GH#2469).\n\tif p := config.GetString(\"issue-prefix\"); p != \"\" {\n\t\tprefix = p","sourceCodeStart":839,"sourceCodeEnd":875,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/ado.go#L839-L875","documentation":"pushADOLinks collects per-link errors returned by resolver.PushLinks and reports each as a 'Link sync ADO #N' warning, both appended to the returned warnings slice and passed to the warn callback. Individual failed relations do not abort the sync; the count of successfully applied links is reduced accordingly.","triggerScenarios":"resolver.PushLinks rejects or fails to write some relations — e.g. ADO rejects a relation type, permission denied on the work item, stale concurrent revision (409), or one desired link's counterpart no longer exists.","commonSituations":"Service account lacking permissions to edit certain ADO work items; work-item revision conflicts from concurrent edits; relation types unsupported by the target ADO process template.","solutions":["Inspect the wrapped %v error for each offending link (permission vs conflict vs invalid relation).","Grant the sync identity permission to edit the affected ADO work items.","Re-run sync to resolve transient revision conflicts; resolve duplicates/stale links in beads first if the error indicates invalid relations."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"count, warnings := pushADOLinks(ctx, ...)\nfor _, w := range warnings {\n\tif strings.HasPrefix(w, \"Link sync ADO #\") {\n\t\tlog.Warn(w) // retry or escalate per-link\n\t}\n}","preventionTips":["Ensure the sync identity has edit permission on all mapped ADO work items.","Avoid concurrent edits to the same ADO work items during sync.","Re-run sync to clear transient revision conflicts."],"tags":["azure-devops","sync","links","warning"],"backgroundTag":"link-sync-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}