gastownhall/beads · error · issueops.ErrSelfDependency
%w: apply batch item %d: %s cannot depend on itself
Error message
%w: apply batch item %d: %s cannot depend on itself
What it means
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.
Source
Thrown at internal/storage/batch_apply.go:265
// gate metadata.
//
// IT RECORDS NOTHING AS TOUCHED, and that is a decision rather than an
// oversight. The ExpectedVersion rule above refuses a guard on a row this
// request has already REWRITTEN, and an edge write is a change to the graph
// rather than to either endpoint's row: the role promises nothing about whether
// it moves the source's version token. A later guard on that source is
// therefore left to the substrate, where a genuine mismatch is
// ErrVersionMismatch — an honest refusal — rather than being refused up front
// as a request a caller could not have composed.
func planApplyBatchDepAdd(item *issueops.DepAddItem, index int, keyIndex map[string]int) error {
if err := validateApplyTargetRef(item.Source, index, "source", keyIndex); err != nil {
return err
}
if err := validateApplyTargetRef(item.Target, index, "target", keyIndex); err != nil {
return err
}
if item.Source == item.Target {
return fmt.Errorf("%w: apply batch item %d: %s cannot depend on itself",
issueops.ErrSelfDependency, index, applyRefLabel(item.Source))
}
if !item.Type.IsValid() {
return fmt.Errorf("%w: apply batch item %d requires a dependency type (max %d chars)",
issueops.ErrValidation, index, types.MaxDependencyTypeLen)
}
metadata, err := normalizeApplyEdgeMetadata(item.Type, item.Metadata)
if err != nil {
return fmt.Errorf("%w: apply batch item %d: %v", issueops.ErrValidation, index, err)
}
item.Metadata = metadata
return nil
}
// checkApplyExpectedVersion refuses a version guard on a row an earlier item of
// this request already mutated.
//
// IT IS A REQUEST-SHAPE RULE, not a race. The token is server-minted andView on GitHub (pinned to 71377f2769)
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
Example fix
// before
edges = append(edges, edge{src, tgt})
// after
if src != tgt { edges = append(edges, edge{src, tgt}) } Defensive patterns
Strategy: validation
Validate before calling
for i, it := range items {
if it.DepAdd == nil { continue }
if refEqual(it.DepAdd.Source, it.DepAdd.Target) {
return fmt.Errorf("dep item %d is a self-dependency", i)
}
} Type guard
func isSelfDep(e *issueops.DepAddItem) bool {
return e != nil && refEqual(e.Source, e.Target)
}
func refEqual(a, b issueops.Ref) bool { return a.ID == b.ID && a.Key == b.Key } Try / catch
if err := store.PlanApplyBatch(plan); err != nil {
var re *issueops.RefError
if errors.Is(err, issueops.ErrSelfDependency) {
// skip or correct the offending edge and rebuild the plan
}
_ = re
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- %w: apply batch item %d requires a dependency type (max %d c
- %w: apply batch item %d: %v
- ExternalDoltConfig: TLSKey set without TLSCert
- db: DependencySQLRepository.Insert: dep must not be nil
- db: DependencySQLRepository.Insert: IssueID must not be empt
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a3624de36807a2c9.
Report an issue: GitHub.