gastownhall/beads · error · issueops.ErrValidation

%w: apply batch item %d requires a dependency type (max %d c

Error message

%w: apply batch item %d requires a dependency type (max %d chars)

What it means

Every DepAdd item requires a dependency Type that passes IsValid() (non-empty and within types.MaxDependencyTypeLen characters). This error fires when the type is missing, empty, or too long. Wraps issueops.ErrValidation.

Source

Thrown at internal/storage/batch_apply.go:269

// 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 and
// rewritten by the write, so mid-request there is no value a caller COULD send:
// the pre-request token is stale by construction, and a row this request just
// created never had one the caller could read. Refusing statically says so;
// letting it through would answer every such request with ErrVersionMismatch

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set a valid dependency type (e.g. "blocks", "related") on the DepAdd item
  2. Truncate or normalize long type values to MaxDependencyTypeLen before batching
  3. Validate the type at edge-generation time and skip/fix invalid rows

Example fix

// before
item := issueops.ApplyItem{Kind: issueops.ItemDepAdd, DepAdd: &issueops.DepAddItem{Source: s, Target: t}}
// after
item := issueops.ApplyItem{Kind: issueops.ItemDepAdd, DepAdd: &issueops.DepAddItem{Source: s, Target: t, Type: "blocks"}}
Defensive patterns

Strategy: validation

Validate before calling

for i, it := range items {
  if it.DepAdd == nil { continue }
  if !it.DepAdd.Type.IsValid() {
    return fmt.Errorf("dep item %d has invalid type %q (max %d chars)", i, it.DepAdd.Type, types.MaxDependencyTypeLen)
  }
}

Type guard

func hasValidDepType(e *issueops.DepAddItem) bool {
  return e != nil && e.Type.IsValid()
}

Try / catch

if err := store.PlanApplyBatch(plan); err != nil {
  if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "requires a dependency type") {
    // set or fix the Type field and rebuild the plan
  }
  return err
}

Prevention

When it happens

Trigger: PlanApplyBatch with DepAdd{Source: ..., Target: ..., Type: ""} or a Type longer than types.MaxDependencyTypeLen; types like "blocks" are valid, empty or oversized strings are not.

Common situations: Forgetting to set Type when constructing DepAdd structs; copying edge data from an export where the type column was blank; a type string that grew past the max length after data changes.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/0ee2457d09101f1f. Report an issue: GitHub.