gastownhall/beads · error

checking dependencies.id: %w

Error message

checking dependencies.id: %w

What it means

This wraps failure of schemaColumnExists when ensureDependenciesIDColumn checks whether `dependencies.id` exists as part of the migration-0053 repair (adding a CHAR(36) id, backfilling it, and keying it). Each step re-verifies its own target state, so a failing inspection aborts the repair before any DDL. It means the metadata query failed, not that the column is missing.

Source

Thrown at internal/storage/schema/migration_repairs.go:431

// 0053's own "REPLACE INTO dependencies (id, ...)" matches rows on the
// uk_dep_* natural-identity unique keys; an unkeyed id lets a REPLACE that
// hits a row whose old depends_on_wisp_id is NULL (so uk_dep_wisp_target
// doesn't match) fall through to INSERT, duplicating the edge under a new id
// while the stale row survives. Restoring id as the PRIMARY KEY is what makes
// REPLACE's own conflict detection do its job.
//
// This is deliberately re-entrant rather than a single "column present ->
// nil" gate: preMigrationRepair's mutations to a synced table like
// dependencies land in the same atomic per-step commit as migration 0053
// (see runMigrations' dirty-table-snapshot ordering), but a process killed
// mid-repair -- after ADD COLUMN, before the backfill or the key finishes --
// still needs the NEXT open's repair call to finish the job rather than
// short-circuit on "column exists". Every step below re-verifies its own
// target state instead of trusting an earlier step ran to completion.
func ensureDependenciesIDColumn(ctx context.Context, db DBConn) error {
	hasID, err := schemaColumnExists(ctx, db, "dependencies", "id")
	if err != nil {
		return fmt.Errorf("checking dependencies.id: %w", err)
	}
	if !hasID {
		if _, err := db.ExecContext(ctx, "ALTER TABLE dependencies ADD COLUMN id CHAR(36) NULL"); err != nil {
			return fmt.Errorf("adding dependencies.id for migration 0053: %w", err)
		}
	}

	if err := backfillDependenciesID(ctx, db); err != nil {
		return err
	}
	return ensureDependenciesIDPrimaryKey(ctx, db)
}

// backfillDependenciesID fills in any dependencies.id still NULL with
// depid.New(issue_id, target) -- the same deterministic id every insert path
// and the post-migration rekeyDependencyIDs pass use (dep_id_backfill.go) --
// so rows with real edges get a real, cross-clone-stable id rather than a
// throwaway placeholder, and rekeyDependencyIDs finds nothing left to correct

View on GitHub (pinned to 71377f2769)

Solutions

  1. Resolve the wrapped driver error first (connectivity/privileges)
  2. Grant the repair user metadata-read access
  3. Re-run the repair — every step re-verifies state, so it is safe to retry from the start
Defensive patterns

Strategy: try-catch

Validate before calling

var n int
if err := db.QueryRowContext(ctx,
    "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'dependencies' AND column_name = 'id'").Scan(&n); err != nil {
    return fmt.Errorf("cannot inspect dependencies columns: %w", err)
}

Try / catch

if err := repairV53RigAndSplitTargets(ctx, db); err != nil {
    if strings.Contains(err.Error(), "checking dependencies.id") {
        db = reconnect(db)
        return repairV53RigAndSplitTargets(ctx, db) // safe: steps re-verify
    }
    return err
}

Prevention

When it happens

Trigger: repairV53RigAndSplitTargets (or the direct unit tests) invokes ensureDependenciesIDColumn and schemaColumnExists errors on the dependencies table — lost connection, privilege denial on column metadata, or driver failure.

Common situations: Under-privileged repair users; network interruption during the multi-step 0053 repair; database restarted between repair steps in CI.

Related errors


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