gastownhall/beads · error

touch dependency coordination: parent ID must not be empty

Error message

touch dependency coordination: parent ID must not be empty

What it means

TouchDependencyCoordinationTableInTx updates the freshness/coordination metadata cell for a dependency table and requires the parent issue ID to identify which shard/key to touch. An empty parent ID means the caller could not determine the owning issue, so the library refuses to write a meaningless coordination key. This is a defensive guard against callers bypassing required parameters.

Source

Thrown at internal/storage/issueops/dependency_coordination.go:35

// touchDependencyCoordinationInTx rewrites the coordination cells for both
// dependency tables in a fixed order. Writers that need a stable view of an
// issue's incoming parent-child edges use these cells to make concurrent Dolt
// transactions conflict rather than cell-merge.
func touchDependencyCoordinationInTx(ctx context.Context, tx DBTX, parentID string) error {
	for _, tier := range [2]string{dependencyCoordinationDurableTier, dependencyCoordinationEphemeralTier} {
		if err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, tier); err != nil {
			return err
		}
	}
	return nil
}

// TouchDependencyCoordinationTableInTx rewrites the coordination cell for one
// dependency table. table must be dependencies or wisp_dependencies.
func TouchDependencyCoordinationTableInTx(ctx context.Context, tx DBTX, parentID, table string) error {
	if parentID == "" {
		return fmt.Errorf("touch dependency coordination: parent ID must not be empty")
	}
	if table != dependencyCoordinationDurableTier && table != dependencyCoordinationEphemeralTier {
		return fmt.Errorf("touch dependency coordination: unsupported table %q", table)
	}
	key := dependencyCoordinationKey(parentID, table)
	if _, err := tx.ExecContext(ctx,
		"REPLACE INTO local_metadata (`key`, value) VALUES (?, ?)", key, strconv.FormatInt(FreshRowLock(), 10)); err != nil {
		return fmt.Errorf("touch dependency coordination for %s: %w", table, err)
	}
	return nil
}

func dependencyCoordinationKey(parentID, table string) string {
	shard := dependencyCoordinationShard(parentID)
	// A tier has 4096 shard rows: enough to keep unrelated writes apart while
	// bounding the clone-local coordination state at 8192 rows. Same-parent
	// operations always resolve to the same shard; a hash collision only adds a
	// safe serialization conflict.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the caller to resolve and pass a non-empty parent issue ID before opening the transaction.
  2. Add earlier validation (reject empty parent ID at the API entry point) to fail fast.
  3. If the parent ID should exist, check the input data — a missing/blank issue ID in the dependency record.

Example fix

// before
if err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil { ... }
// after
if parentID == "" {
    return fmt.Errorf("cannot touch dependency coordination: empty parent ID")
}
if err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil { ... }
Defensive patterns

Strategy: validation

Validate before calling

if parentID == "" {
    return fmt.Errorf("parent ID required before touching dependency coordination")
}
err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table)

Type guard

func validParentID(id string) bool { return id != "" }

Try / catch

if err := TouchDependencyCoordinationTableInTx(ctx, tx, parentID, table); err != nil {
    return fmt.Errorf("touch coordination (parent=%q): %w", parentID, err)
}

Prevention

When it happens

Trigger: Calling TouchDependencyCoordinationTableInTx with parentID="" — typically from a caller path (PersistDependenciesWithOptionsResult, addDependencyInTx, touchDependencyCoordinationInTx) where the parent issue ID was not resolved before the transaction ran.

Common situations: A bug in dependency persistence where an empty parent ID slips through earlier validation; hand-written code or tests invoking the internal touch helper without an ID; data corruption where the parent lookup returned empty.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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