gastownhall/beads · error

probing migration lock %q: %w

Error message

probing migration lock %q: %w

What it means

migrationLockFree probes the database-scoped migration lock with SELECT IS_FREE_LOCK(?) — a read that never queues or acquires. This error wraps a failure of that IS_FREE_LOCK query itself. A NULL result is handled separately (fail closed to the locked path, no error); only an actual query error produces this message.

Source

Thrown at internal/storage/schema/converged.go:169

		return false, "", fmt.Errorf("selecting database %q: %w", databaseName, err)
	}
	if quoted == "" {
		return false, "", fmt.Errorf("selecting database %q: selector returned no quoted name", databaseName)
	}
	return true, quoted, nil
}

// migrationLockFree reports whether the database-scoped migration lock is
// currently unheld. IS_FREE_LOCK is a read: it never queues, never acquires,
// and costs one round trip, which is the entire point — the fast path exists
// to stop paying GET_LOCK's queue.
//
// A NULL answer means the server would not tell us, which is not the same as
// "free": fail closed.
func migrationLockFree(ctx context.Context, db DBConn, lockName string) (bool, error) {
	var free sql.NullInt64
	if err := db.QueryRowContext(ctx, "SELECT IS_FREE_LOCK(?)", lockName).Scan(&free); err != nil {
		return false, fmt.Errorf("probing migration lock %q: %w", lockName, err)
	}
	if !free.Valid {
		return false, nil
	}
	return free.Int64 == 1, nil
}

// doltIgnoreSeeded reports whether every canonical dolt_ignore pattern
// seedDoltIgnorePatterns would assert is already present. It is the read-only
// counterpart of that seed and shares its version gate: a pattern whose flip
// migration has not been reached yet is not expected, exactly as the seed
// would not insert it. mainVersionAtLeast is a lower bound on the main cursor
// that the caller has already established, so this read costs one round trip
// rather than three.
//
// Presence is judged on the pattern alone, never on its ignored value, because
// INSERT IGNORE would leave an explicit operator override (a pattern recorded
// with ignored=false) untouched. Reporting such a row as missing would send

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry — the probe is read-only and alreadyConverged fails closed (falls back to the normal locked path), so the error may be transient
  2. Inspect the wrapped %w driver error; if connection-level, reconnect and re-run bd
  3. If IS_FREE_LOCK is unsupported by your Dolt server version, upgrade the server or skip the lock-free fast path
  4. Check context timeouts if deadlines recur on a busy shared server
Defensive patterns

Strategy: fallback

Validate before calling

// verify the server supports named locks before fast-path probing
var v string
_ = db.QueryRow("SELECT VERSION()").Scan(&v) // check Dolt version supports IS_FREE_LOCK

Try / catch

if _, err := alreadyConverged(...); err != nil {
    // alreadyConverged errors are advisory: proceed down the locked MigrateUp path
    return MigrateUp(ctx, db)
}

Prevention

When it happens

Trigger: The SELECT IS_FREE_LOCK(?) query fails: broken pooled connection, context cancelled/deadline exceeded, server rejected the statement, or the driver errored mid-scan.

Common situations: Dolt sql-server connection dropped between earlier probes and this one; context timeout during a slow open on a loaded shared rig; a Dolt build/server version that rejects or errors on IS_FREE_LOCK.

Related errors


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