gastownhall/beads · error

dependencies: %w

Error message

dependencies: %w

What it means

rekeyDependencyIDs rewrites dependencies.id values to deterministic depid.New(issue_id, target) values so independently-migrated clones converge and `bd dolt pull` works. This error wraps any failure from the re-key pass over the dependencies table specifically (the wisp_dependencies variant has its own wrapper).

Source

Thrown at internal/storage/schema/dep_id_backfill.go:32

//
// Migration 0043 minted dependencies.id from DEFAULT (UUID()), which is
// per-clone-random; migration 0050 + the deterministic insert paths fix new
// rows, but rows that already exist on an upgrading clone still carry the random
// id. Leaving them would keep two independently-migrated clones divergent (same
// edge, different primary key) and break `bd dolt pull`. This rewrites them to
// the deterministic value so two clones converge to byte-identical dependencies.
//
// It runs from MigrateUp right after the schema migrations (so 0050 has already
// asserted the canonical schema), and only on a pass where migration work was
// needed — it is not part of the steady-state open path. It is idempotent: a row
// already keyed deterministically is skipped, so re-running on a later migration
// pass is a cheap no-op. dependencies changes are staged and committed by
// MigrateUp; wisp_dependencies is dolt-ignored, so its re-key stays clone-local
// (it only escapes on promotion, which copies the id).
func rekeyDependencyIDs(ctx context.Context, db DBConn) (bool, error) {
	wroteDeps, err := rekeyDependencyTable(ctx, db, "dependencies")
	if err != nil {
		return wroteDeps, fmt.Errorf("dependencies: %w", err)
	}
	wroteWisp, err := rekeyDependencyTable(ctx, db, "wisp_dependencies")
	if err != nil {
		return wroteDeps || wroteWisp, fmt.Errorf("wisp_dependencies: %w", err)
	}
	return wroteDeps || wroteWisp, nil
}

// rekeyDependencyTable re-derives ids for one edge table. table must be a
// hardcoded constant ("dependencies" or "wisp_dependencies").
func rekeyDependencyTable(ctx context.Context, db DBConn, table string) (bool, error) {
	// Skip cleanly if the table or its id column isn't present (e.g. an older or
	// partial schema where the surrogate key was never added): nothing to re-key.
	hasID, err := columnExists(ctx, db, table, "id")
	if err != nil {
		return false, err
	}
	if !hasID {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped inner error (from the SELECT/UPDATE inside rekeyDependencyTable) for the root cause
  2. Check for unique-key violations: duplicate (issue_id, target) edges collide on the same deterministic id — dedupe them
  3. Re-run the migration pass — the re-key is idempotent and resumes
  4. If the schema predates migration 0043/0050, run the full migration path (normal MigrateUp) rather than partial copies
Defensive patterns

Strategy: try-catch

Validate before calling

-- detect rows needing rekey and duplicates before migrating
SELECT issue_id, COALESCE(depends_on_issue_id, depends_on_wisp_id, depends_on_external) t, COUNT(*) c
FROM dependencies GROUP BY issue_id, t HAVING c > 1;

Try / catch

if _, err := rekeyDependencyIDs(ctx, db); err != nil {
    if dberrors.IsUniqueViolation(err) {
        return fmt.Errorf("duplicate dependency edges; dedupe before rekey: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: rekeyDependencyTable(ctx, db, "dependencies") fails — columnExists probe error, the SELECT of (id, issue_id, COALESCE(...)) fails, row scan failure, rows.Err(), or an UPDATE during the re-key loop errors.

Common situations: Upgrading an old clone whose dependencies table has pre-0050 UUID ids; connection drop mid-migration; a malformed row or unique-key conflict when two edges map to the same deterministic id; schema drift where the id column layout differs.

Related errors


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