gastownhall/beads · warning · ErrMigrationLockRelease
schema: release migration lock: %w: %w
Error message
schema: release migration lock: %w: %w
What it means
ReleaseMigrationLock executes RELEASE_LOCK and the query itself failed (returned a non-nil error); the sentinel ErrMigrationLockRelease is wrapped along with the driver error. The connection is discarded (marked driver.ErrBadConn) so it is not returned to the pool in an unknown state. This signals the lock release could not be confirmed, not necessarily that the lock is still held.
Source
Thrown at internal/storage/schema/lock.go:372
if !locked.Valid {
return fmt.Errorf("schema: acquire migration lock: %w: returned NULL", ErrMigrationLockUnavailable)
}
if locked.Int64 != 1 {
return fmt.Errorf("schema: acquire migration lock: %w: timeout", ErrMigrationLockUnavailable)
}
return nil
}
// ReleaseMigrationLock releases the named schema migration lock from the same
// pinned Dolt/MySQL session used to acquire it.
func ReleaseMigrationLock(conn *sql.Conn, lockName string) error {
cleanupCtx, cancel := context.WithTimeout(context.Background(), migrationLockCleanupTimeout)
defer cancel()
var released sql.NullInt64
if err := conn.QueryRowContext(cleanupCtx, "SELECT RELEASE_LOCK(?)", lockName).Scan(&released); err != nil {
discardConn(conn)
return fmt.Errorf("schema: release migration lock: %w: %w", ErrMigrationLockRelease, err)
}
if !released.Valid {
discardConn(conn)
return fmt.Errorf("schema: release migration lock: %w: returned NULL", ErrMigrationLockRelease)
}
if released.Int64 != 1 {
discardConn(conn)
return fmt.Errorf("schema: release migration lock: %w: returned %d", ErrMigrationLockRelease, released.Int64)
}
return nil
}
func discardConn(conn *sql.Conn) {
_ = conn.Raw(func(driverConn any) error {
return driver.ErrBadConn
})
}
View on GitHub (pinned to 71377f2769)
Solutions
- Retry release on a fresh pinned connection to the same session/database if possible.
- Rely on session teardown: closing the connection (or process exit) implicitly releases any GET_LOCK held by that session.
- Increase migrationLockCleanupTimeout or keep-alive if long migrations routinely exceed it.
- Check server logs/connectivity for the underlying driver error wrapped in the message.
Example fix
// before
if err := schema.ReleaseMigrationLock(conn, lockName); err != nil {
log.Fatal(err)
}
// after: release failure usually means dead session; close and continue
if err := schema.ReleaseMigrationLock(conn, lockName); err != nil {
conn.Close() // session teardown releases session-scoped locks
log.Printf("release failed, connection discarded: %v", err)
} Defensive patterns
Strategy: try-catch
Try / catch
if err := schema.ReleaseMigrationLock(conn, name); err != nil {
if errors.Is(err, schema.ErrMigrationLockRelease) {
conn.Close() // session teardown implicitly releases session-scoped locks
}
} Prevention
- Always release on the same pinned *sql.Conn used to acquire.
- Keep the session alive across the migration (avoid long idle timeouts).
- Remember closing the connection releases the lock implicitly — release errors are often benign after teardown.
- Keep migrations short relative to migrationLockCleanupTimeout.
When it happens
Trigger: Calling ReleaseMigrationLock when the pinned session has died, the network dropped, the cleanup timeout (migrationLockCleanupTimeout) expired, or the server rejects RELEASE_LOCK for the session.
Common situations: Long migrations exceeding the cleanup timeout; MySQL server gone away after an idle period; Dolt server restart mid-migration; firewall/load-balancer closing idle connections.
Related errors
- dolt server connection failed: %w
- failed to reach the workspace identity: %v
- begin tx: %w
- %w The Dolt database is locked.%s Try: bd doctor --fix (cle
- begin read tx: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b016a3260ecdcf7f.
Report an issue: GitHub.