gastownhall/beads · error
adding the wisp_dependencies id primary key for the 0058 rep
Error message
adding the wisp_dependencies id primary key for the 0058 repair: %w
What it means
Wraps a failure when adding the primary key on the existing `id` column (`ALTER TABLE wisp_dependencies ADD PRIMARY KEY (id)`) in the crash-recovery path where the column survived but its key did not. The ADD PRIMARY KEY failed, leaving wisp_dependencies without a primary key and aborting the repair.
Source
Thrown at internal/storage/schema/wisp_dep_forward_repair.go:413
return err
}
if !hasID {
if _, err := db.ExecContext(ctx,
"ALTER TABLE wisp_dependencies ADD COLUMN id CHAR(36) NOT NULL DEFAULT (UUID()) PRIMARY KEY FIRST"); err != nil {
return fmt.Errorf("adding wisp_dependencies.id for the 0058 repair: %w", err)
}
return nil
}
// id survived a crash but its key did not (or the column predates the
// key): add the key alone rather than re-adding the column.
hasPK, err := schemaHasPrimaryKey(ctx, db, wispDepTable)
if err != nil {
return err
}
if !hasPK {
if _, err := db.ExecContext(ctx, "ALTER TABLE wisp_dependencies ADD PRIMARY KEY (id)"); err != nil {
return fmt.Errorf("adding the wisp_dependencies id primary key for the 0058 repair: %w", err)
}
}
return nil
}
// dedupeWispDepNaturalIdentity removes rows that would collide on the three
// uk_* unique keys added next. Duplicates are reachable because the legacy
// shape's composite primary key covers (issue_id, depends_on_id) -- so two rows
// differing only in a column the COALESCE did not select were legal -- and
// because the keyless window leaves writes unconstrained.
//
// The comparison is null-safe (<=>): the target columns are NULL for every
// target kind a row does not use, and ordinary = would treat two identical
// wisp-target rows as distinct because their NULL issue targets never compare
// equal. MIN(id) is an arbitrary but deterministic survivor, which is the
// property that matters -- it makes a resumed run pick the same row.
func dedupeWispDepNaturalIdentity(ctx context.Context, db DBConn) error {
if _, err := db.ExecContext(ctx, `View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped cause: duplicates/NULLs in id → dedupe and backfill (UPDATE ... SET id = UUID() WHERE id IS NULL) before re-running the repair.
- Remove duplicate rows first (the repair's dedupeWispDepNaturalIdentity handles natural-key collisions; fix id duplicates separately).
- Grant ALTER/INDEX to the migration user.
- Re-run `bd` so the guarded repair completes the missing PK step.
Example fix
// before: ADD PRIMARY KEY (id) fails — NULL ids from a crashed pass // after: backfill then resume UPDATE wisp_dependencies SET id = UUID() WHERE id IS NULL OR id = ''; -- then re-run: bd (resumes guarded repair) ALTER TABLE wisp_dependencies ADD PRIMARY KEY (id);
Defensive patterns
Strategy: validation
Validate before calling
// verify id values are unique and non-null before the PK step resumes
var dupes, nulls int
db.QueryRowContext(ctx, `SELECT COUNT(*) - COUNT(DISTINCT id), SUM(id IS NULL OR id = '') FROM wisp_dependencies`).Scan(&dupes, &nulls)
if dupes > 0 || nulls > 0 {
log.Fatalf("id column not PK-ready: %d duplicates, %d nulls — backfill before repair", dupes, nulls)
} Try / catch
if err := repairWispDependenciesForwardShape(ctx, db); err != nil {
if strings.Contains(err.Error(), "adding the wisp_dependencies id primary key") {
// backfill NULL ids and remove duplicates, then re-run the guarded repair
return fmt.Errorf("id column not PK-ready after crashed pass: %w", err)
}
return err
} Prevention
- Do not kill the process between ADD COLUMN id and ADD PRIMARY KEY steps.
- Pre-check id uniqueness/nullability after any crashed repair.
- Grant ALTER/INDEX privileges to the migration user.
- Run the repair to completion once started; it is guarded and resumable via re-run.
When it happens
Trigger: ensureWispDepSurrogateKey finds id present but no PK, then the ADD PRIMARY KEY DDL errors — NULL ids from a partially backfilled crash, duplicate id values, privilege denial, or lock timeout.
Common situations: A prior pass was killed between ADD COLUMN and ADD PRIMARY KEY, leaving NULL or duplicate UUIDs in id; DB user lacking ALTER/INDEX privileges; concurrent writers blocking the ALTER.
Related errors
- dropping the wisp_dependencies primary key for the 0058 repa
- adding wisp_dependencies.id for the 0058 repair: %w
- pre-repair for migration %s: %w
- dropping idx_wisp_dep_type_target for the 0058 repair: %w
- dropping %s for the 0058 repair: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3f3e59080422f0e9.
Report an issue: GitHub.