gastownhall/beads · error

failed to parse DSN for migration connection: %w

Error message

failed to parse DSN for migration connection: %w

What it means

openMigrationDB parses the store's connection string with mysql.ParseDSN before opening a dedicated long-timeout migration connection; this error wraps a DSN parse failure. It is a configuration problem: the connection string is malformed, not a server-side issue.

Source

Thrown at internal/storage/dolt/store.go:2743

// per-database advisory lock, with retry for transient lock contention.
// Implements storage.SchemaMigrator.
func (s *DoltStore) ApplySchemaMigrations(ctx context.Context) (int, error) {
	migDB, err := s.openMigrationDB()
	if err != nil {
		return 0, err
	}
	defer migDB.Close()
	return initSchemaOnDBWithRetry(ctx, migDB)
}

// openMigrationDB opens a one-off connection pool for schema migrations with no
// read/write timeout. Migrations may run far longer than the default 10s pool
// timeout, and timing out part-way leaves the database in a dirty, half-migrated
// state. The single connection is closed by the caller once migration completes.
func (s *DoltStore) openMigrationDB() (*sql.DB, error) {
	cfg, err := mysql.ParseDSN(s.connStr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse DSN for migration connection: %w", err)
	}
	cfg.ReadTimeout = 0
	cfg.WriteTimeout = 0
	db, err := sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		return nil, fmt.Errorf("failed to open migration connection: %w", err)
	}
	db.SetMaxOpenConns(1)
	return db, nil
}

// rebuildPoolAfterMigration replaces the main connection pool (s.db) after a
// migrating open. Migrations run over a separate one-off pool
// (openMigrationDB); a connection already pooled in s.db before migrations
// ran (e.g. the startup Ping in newServerMode) stays pinned to the
// pre-migration Dolt session root, so the first read through it returns 0
// rows / table-not-found and does not self-heal on retry (be-itm5). A
// non-migrating open (applied == 0 — the common re-open-of-an-

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the DSN string: correct form is user:pass@tcp(host:port)/dbname?params
  2. URL-escape or cfg-escape special characters in the password (e.g. use url.QueryEscape for the password portion when building the DSN)
  3. Use mysql.Config + cfg.FormatDSN() in code instead of hand-writing the string
  4. Check the environment variable / config file supplying the connection string for truncation or quoting artifacts

Example fix

// before
connStr := "root:p@ss@tcp(localhost:3306)/beads"
// after
connStr := "root:p%40ss@tcp(localhost:3306)/beads"
Defensive patterns

Strategy: validation

Validate before calling

// validate the DSN before constructing the store
func validateDSN(connStr string) error {
    cfg, err := mysql.ParseDSN(connStr)
    if err != nil {
        return fmt.Errorf("invalid DSN: %w", err)
    }
    if cfg.DBName == "" {
        return errors.New("DSN missing database name")
    }
    return nil
}

Prevention

When it happens

Trigger: Creating a DoltStore with s.connStr that mysql.ParseDSN rejects — malformed format (wrong scheme, bad net address, unterminated brackets), invalid parameter, or characters that need escaping (e.g. special chars in password like '@' or '/' unescaped).

Common situations: Password containing '@', '(', '/' or spaces placed raw into user:pass@tcp(...)/ DSN; env var BEADS_DSN missing or set to a non-DSN value; copy-pasted URI-style 'mysql://user:pass@host/db' which ParseDSN does not accept directly.

Understand the failure class

Related errors


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