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

  1. Check the wrapped error: ER_DUP_KEYNAME means the index now exists — re-run the repair and it will no-op
  2. Grant INDEX privilege to the migration user
  3. Serialize repair runs so only one process performs DDL
  4. 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

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


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