gastownhall/beads · critical
migration 0053: %d dependencies row(s) have no depends_on_is
Error message
migration 0053: %d dependencies row(s) have no depends_on_issue_id/depends_on_wisp_id/depends_on_external target and cannot be assigned an id (ck_dep_one_target should prevent this); repair manually before retrying
What it means
After backfill, some `dependencies` rows still have NULL id because all three target columns (depends_on_issue_id, depends_on_wisp_id, depends_on_external) are NULL — there is nothing to derive an id from. The `ck_dep_one_target` check constraint should make this impossible, so these rows are drifted data. The repair aborts with the row count instead of keying the table with NULL ids.
Source
Thrown at internal/storage/schema/migration_repairs.go:523
// ensureDependenciesIDPrimaryKey finishes restoring dependencies.id to 0043's
// canonical shape: NOT NULL and the table's PRIMARY KEY. It re-verifies both
// independently of whether this pass just backfilled anything, so a re-entry
// after a crash between the backfill and the key (or between MODIFY NOT NULL
// and ADD PRIMARY KEY) finishes the remaining step(s) instead of re-running
// ones already done -- MODIFY COLUMN restating an identical definition and
// re-adding an already-present PRIMARY KEY are otherwise either redundant or
// outright rejected as a duplicate key.
func ensureDependenciesIDPrimaryKey(ctx context.Context, db DBConn) error {
var remainingNull int
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dependencies WHERE id IS NULL").Scan(&remainingNull); err != nil {
return fmt.Errorf("counting unbackfilled dependencies.id rows for migration 0053: %w", err)
}
if remainingNull > 0 {
// Fail with an actionable count now rather than let a subsequent
// MODIFY COLUMN ... NOT NULL below abort with a generic "column
// cannot be null" error, or silently key the table while leaving
// NULL-id rows behind it.
return fmt.Errorf("migration 0053: %d dependencies row(s) have no depends_on_issue_id/depends_on_wisp_id/depends_on_external target and cannot be assigned an id (ck_dep_one_target should prevent this); repair manually before retrying", remainingNull)
}
idIsPrimaryKey, err := schemaColumnInPrimaryKey(ctx, db, "dependencies", "id")
if err != nil {
return fmt.Errorf("checking dependencies.id primary key: %w", err)
}
if idIsPrimaryKey {
return nil
}
if _, err := db.ExecContext(ctx, "ALTER TABLE dependencies MODIFY COLUMN id CHAR(36) NOT NULL"); err != nil {
return fmt.Errorf("finalizing dependencies.id for migration 0053: %w", err)
}
hasAnyPrimaryKey, err := schemaHasPrimaryKey(ctx, db, "dependencies")
if err != nil {
return fmt.Errorf("checking dependencies for an existing primary key: %w", err)
}View on GitHub (pinned to 71377f2769)
Solutions
- Find the offending rows: SELECT * FROM dependencies WHERE id IS NULL
- Delete genuinely orphaned rows or set one of depends_on_issue_id / depends_on_wisp_id / depends_on_external so exactly one target is present
- Verify ck_dep_one_target exists on the table and re-add it if missing
- Rerun `bd` — the repair recounts and proceeds when remainingNull == 0
Example fix
// before: drifted rows block migration // after: repair or remove rows, then retry bd dolt sql -q "DELETE FROM dependencies WHERE id IS NULL AND depends_on_issue_id IS NULL AND depends_on_wisp_id IS NULL AND depends_on_external IS NULL" ; bd ready
Defensive patterns
Strategy: validation
Validate before calling
-- run before upgrading; must return zero rows SELECT issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external FROM dependencies WHERE depends_on_issue_id IS NULL AND depends_on_wisp_id IS NULL AND depends_on_external IS NULL;
Try / catch
if err := ensureSchema(ctx, db); err != nil {
var n int
if _, scanErr := fmt.Sscanf(err.Error(), "migration 0053: %d dependencies row(s)", &n); scanErr == nil {
return fmt.Errorf("run repair SQL for %d orphan dependency rows, then retry", n)
}
return err
} Prevention
- Never insert dependencies rows via raw SQL bypassing the app layer
- Keep ck_dep_one_target in place; re-add after manual schema edits
- Upgrade through each intermediate beads version rather than skipping schema eras
- Audit orphan rows after imports/merges
When it happens
Trigger: ensureDependenciesIDPrimaryKey finds remainingNull > 0 during the 0053 repair — typically a pre-constraint database (written before ck_dep_one_target was added) or rows inserted by a buggy/old version or manual SQL bypassing constraints.
Common situations: Databases migrated across several beads versions where ck_dep_one_target never ran; rows hand-inserted with `bd dolt sql` omitting target columns; a failed partial import/merge leaving orphan dependency rows.
Related errors
- checking constraint %s on %s: %w
- pending ignored schema migrations alter pre-existing dirty t
- pre-existing dirty tables changed during schema migration: %
- issue %s waiters: %w
- legacy SQLite issue has empty ID
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f2907219d0c888e4.
Report an issue: GitHub.