{"record":{"id":"a3624de36807a2c9","repo":"gastownhall/beads","slug":"w-apply-batch-item-d-s-cannot-depend-on-itsel","errorCode":null,"errorMessage":"%w: apply batch item %d: %s cannot depend on itself","messagePattern":"%w: apply batch item (.+?): (.+?) cannot depend on itself","errorType":"validation","errorClass":"issueops.ErrSelfDependency","httpStatus":null,"severity":"error","filePath":"internal/storage/batch_apply.go","lineNumber":265,"sourceCode":"// gate metadata.\n//\n// IT RECORDS NOTHING AS TOUCHED, and that is a decision rather than an\n// oversight. The ExpectedVersion rule above refuses a guard on a row this\n// request has already REWRITTEN, and an edge write is a change to the graph\n// rather than to either endpoint's row: the role promises nothing about whether\n// it moves the source's version token. A later guard on that source is\n// therefore left to the substrate, where a genuine mismatch is\n// ErrVersionMismatch — an honest refusal — rather than being refused up front\n// as a request a caller could not have composed.\nfunc planApplyBatchDepAdd(item *issueops.DepAddItem, index int, keyIndex map[string]int) error {\n\tif err := validateApplyTargetRef(item.Source, index, \"source\", keyIndex); err != nil {\n\t\treturn err\n\t}\n\tif err := validateApplyTargetRef(item.Target, index, \"target\", keyIndex); err != nil {\n\t\treturn err\n\t}\n\tif item.Source == item.Target {\n\t\treturn fmt.Errorf(\"%w: apply batch item %d: %s cannot depend on itself\",\n\t\t\tissueops.ErrSelfDependency, index, applyRefLabel(item.Source))\n\t}\n\tif !item.Type.IsValid() {\n\t\treturn fmt.Errorf(\"%w: apply batch item %d requires a dependency type (max %d chars)\",\n\t\t\tissueops.ErrValidation, index, types.MaxDependencyTypeLen)\n\t}\n\tmetadata, err := normalizeApplyEdgeMetadata(item.Type, item.Metadata)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: apply batch item %d: %v\", issueops.ErrValidation, index, err)\n\t}\n\titem.Metadata = metadata\n\treturn nil\n}\n\n// checkApplyExpectedVersion refuses a version guard on a row an earlier item of\n// this request already mutated.\n//\n// IT IS A REQUEST-SHAPE RULE, not a race. The token is server-minted and","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/batch_apply.go#L247-L283","documentation":"A dependency edge item where Source and Target resolve to the same issue is a self-dependency, which is rejected with issueops.ErrSelfDependency. Self-edges would create meaningless (or cycle-of-one) dependency relationships in the graph.","triggerScenarios":"PlanApplyBatch with a DepAdd item whose Source and Target refs (IDs or keys) both resolve to the same issue — e.g. DepAdd{Source: Ref{Key: \"a\"}, Target: Ref{Key: \"a\"}} or both refs pointing at the same key declared once.","commonSituations":"Generating edges from a data table where source and target columns happen to match on a row; templating edges like (x, parent-of-x) where x is its own parent in bad data.","solutions":["Skip edge rows where source equals target before building the batch","Fix upstream data so no issue depends on itself","Add a unit test on edge generation asserting src != tgt"],"exampleFix":"// before\nedges = append(edges, edge{src, tgt})\n// after\nif src != tgt { edges = append(edges, edge{src, tgt}) }","handlingStrategy":"validation","validationCode":"for i, it := range items {\n  if it.DepAdd == nil { continue }\n  if refEqual(it.DepAdd.Source, it.DepAdd.Target) {\n    return fmt.Errorf(\"dep item %d is a self-dependency\", i)\n  }\n}","typeGuard":"func isSelfDep(e *issueops.DepAddItem) bool {\n  return e != nil && refEqual(e.Source, e.Target)\n}\nfunc refEqual(a, b issueops.Ref) bool { return a.ID == b.ID && a.Key == b.Key }","tryCatchPattern":"if err := store.PlanApplyBatch(plan); err != nil {\n  var re *issueops.RefError\n  if errors.Is(err, issueops.ErrSelfDependency) {\n    // skip or correct the offending edge and rebuild the plan\n  }\n  _ = re\n  return err\n}","preventionTips":["Filter src==tgt rows at edge-generation time","Sanity-check source data for rows where an issue references itself","Add tests covering degenerate edge inputs"],"tags":["validation","dependency","self-dependency","batch-apply"],"backgroundTag":"self-dependency","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}