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

  1. Check the wrapped cause: duplicates/NULLs in id → dedupe and backfill (UPDATE ... SET id = UUID() WHERE id IS NULL) before re-running the repair.
  2. Remove duplicate rows first (the repair's dedupeWispDepNaturalIdentity handles natural-key collisions; fix id duplicates separately).
  3. Grant ALTER/INDEX to the migration user.
  4. 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

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


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