gastownhall/beads · critical
creating idx_wisps_is_blocked: %w
Error message
creating idx_wisps_is_blocked: %w
What it means
This wraps failure of `CREATE INDEX idx_wisps_is_blocked ON wisps(is_blocked, status)` after the repair confirmed the index does not exist. This is a real DDL failure: the schema is intentionally being changed and the statement was rejected. Common causes are duplicate-index races (another process created it concurrently), missing INDEX privilege, lock contention, or key-length/limit issues on the indexed columns.
Source
Thrown at internal/storage/schema/migration_repairs.go:297
}
if _, err := db.ExecContext(ctx, "ALTER TABLE wisps ADD COLUMN is_blocked TINYINT(1) NOT NULL DEFAULT 0"); err != nil {
return fmt.Errorf("adding wisps.is_blocked: %w", err)
}
return nil
}
// ensureWispIsBlockedIndex is ignored/0006's CREATE INDEX statement,
// translated to Go alongside ensureWispIsBlockedColumn above.
func ensureWispIsBlockedIndex(ctx context.Context, db DBConn) error {
hasIndex, err := schemaIndexExists(ctx, db, "wisps", "idx_wisps_is_blocked")
if err != nil {
return fmt.Errorf("checking idx_wisps_is_blocked index: %w", err)
}
if hasIndex {
return nil
}
if _, err := db.ExecContext(ctx, "CREATE INDEX idx_wisps_is_blocked ON wisps(is_blocked, status)"); err != nil {
return fmt.Errorf("creating idx_wisps_is_blocked: %w", err)
}
return nil
}
// wispsTableDDLForMigration0047 is 0020_create_wisps.up.sql's shape plus every
// column a main migration <=52 subsequently adds to wisps: no_history (0023)
// and started_at (0027). wisps is dolt_ignore'd (never replicated), so
// creating it here when it is missing cannot fork any synced clone: the
// table itself is always clone-local by design, and the ignored sequence's
// own guarded create (ignored/0001: CREATE a __temp__wisps, then RENAME it
// to wisps only if wisps does not already exist, else DROP the temp table)
// simply takes the DROP branch once this has run.
const wispsTableDDLForMigration0047 = `CREATE TABLE IF NOT EXISTS wisps (
id VARCHAR(255) PRIMARY KEY,
content_hash VARCHAR(64),
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL DEFAULT '',
design TEXT NOT NULL DEFAULT '',View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error: ER_DUP_KEYNAME means the index now exists — re-run the repair and it will no-op
- Grant INDEX privilege to the migration user
- Serialize repair runs so only one process performs DDL
- Raise lock_wait_timeout or run the index build during a maintenance window
Example fix
// before: blind retry loop hammering DDL
for { if err := runRepair(db); err == nil { break } }
// after: re-run once after confirming state
if err := runRepair(db); err != nil {
if existsErr := recheckIndex(db, "idx_wisps_is_blocked"); existsErr == nil {
return nil // other process created it
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// check for an equivalent composite index before letting repair build it
rows, err := db.QueryContext(ctx,
"SELECT index_name FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'wisps' AND column_name IN ('is_blocked','status')")
if err == nil {
defer rows.Close() // log any existing covering indexes
} Try / catch
if err := ensureWispIsBlockedForRecompute(ctx, db); err != nil {
var drv *mysql.MySQLError
if errors.As(err, &drv) {
switch drv.Number {
case 1061: // ER_DUP_KEYNAME — index created concurrently
return ensureWispIsBlockedForRecompute(ctx, db) // re-run will no-op
case 1205:
return fmt.Errorf("index build blocked by lock; retry later: %w", err)
case 1071: // key too long
return fmt.Errorf("engine key-length limit: %w", err)
}
}
return err
} Prevention
- Serialize DDL repairs across processes
- Grant INDEX privilege to the repair user
- Treat ER_DUP_KEYNAME as success and re-run the repair to confirm
- Build indexes on large tables during low-traffic windows
When it happens
Trigger: ensureWispIsBlockedIndex ran where idx_wisps_is_blocked was absent and ExecContext CREATE INDEX fails: ER_DUP_KEYNAME from a concurrent creator, missing INDEX/ALTER privilege, DDL lock timeout, or storage-engine limits on composite index size.
Common situations: Concurrent tool instances repairing the same clone; running as a user that can write data but not schema; large wisps tables making index builds exceed lock_wait_timeout on older MySQL.
Related errors
- adding wisps.is_blocked: %w
- checking idx_wisps_is_blocked index: %w
- adding dependencies.id for migration 0053: %w
- schema: read database name: %w
- schema migration: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9bae299aba0f369e.
Report an issue: GitHub.