gastownhall/beads · error
invalid ref: %w
Error message
invalid ref: %w
What it means
ReadMigrationContentHashes wraps any validation failure from validateMigrationRef in "invalid ref: %w". The underlying cause (empty, too long, or bad characters) is always wrapped, so use errors.Is/Unwrap to see the specific reason. It is the entry guard before interpolating the ref into AS OF queries.
Source
Thrown at internal/storage/schema/migration_content_hashes.go:50
// ReadMigrationContentHashes reads version -> content_hash from schema_migrations,
// either at HEAD (ref == "") or AS OF ref (e.g. "remotes/origin/main"). NULL/empty
// hashes are dropped. It returns an error when the table, column, or ref is
// unavailable; the caller classifies it with RemoteRefUnavailableErr /
// MissingMigrationObjectErr.
//
// Dolt requires a literal ref in AS OF: bind parameters (including inside CONCAT)
// fail server-side with `unbound variable "v1" in query`, so the validated ref is
// interpolated into the SQL text (bd-6dnrw.27).
func ReadMigrationContentHashes(ctx context.Context, db DBConn, ref string) (map[int]string, error) {
var (
rows *sql.Rows
err error
)
if ref == "" {
rows, err = db.QueryContext(ctx, "SELECT version, content_hash FROM schema_migrations")
} else {
if verr := validateMigrationRef(ref); verr != nil {
return nil, fmt.Errorf("invalid ref: %w", verr)
}
// A cached ref may legitimately predate schema_migrations or its
// content_hash column. Probe the historical shape before selecting from
// it: letting the SELECT fail emits a Dolt server warning for every
// read-only doctor run.
//nolint:gosec // G201: ref is validated above — AS OF requires a literal, not a bind param
hasTable, queryErr := queryHasRows(ctx, db,
fmt.Sprintf("SHOW TABLES AS OF '%s' LIKE 'schema_migrations'", ref))
if queryErr != nil {
return nil, queryErr
}
if !hasTable {
return nil, fmt.Errorf("table not found: schema_migrations at %q", ref)
}
//nolint:gosec // G201: ref is validated above — AS OF requires a literal, not a bind param
hasContentHash, queryErr := queryHasRows(ctx, db,
fmt.Sprintf("SHOW COLUMNS FROM schema_migrations AS OF '%s' LIKE 'content_hash'", ref))
if queryErr != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Fix the ref to be a non-empty, <=128-char, allowlist-conforming string like "remotes/origin/main".
- Check the wrapped cause with errors.Is to know which rule failed.
- Pass "" only when HEAD is intended via the ref=="" code path.
- Sanitize ref inputs at the boundary (CLI/config) before calling.
Example fix
// before
hashes, err := schema.ReadMigrationContentHashes(ctx, db, raw)
// after
ref := strings.TrimSpace(raw)
if ref != "" && (len(ref) > 128 || !refRe.MatchString(ref)) {
return fmt.Errorf("bad ref %q", ref)
}
hashes, err := schema.ReadMigrationContentHashes(ctx, db, ref) Defensive patterns
Strategy: validation
Validate before calling
func migrationRefOK(ref string) bool {
return ref == "" || (len(ref) <= 128 &&
regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref))
} Type guard
func validRef(ref string) bool {
return ref == "" || (len(ref) <= 128 &&
regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref))
} Try / catch
if err != nil {
var unwrapped = errors.Unwrap(err)
// inspect wrapped cause: empty / too long / invalid format
} Prevention
- Validate refs once at the CLI/config boundary.
- Unwrap the error to identify which rule failed before retrying.
- Use "" only intentionally (HEAD path).
When it happens
Trigger: Calling ReadMigrationContentHashes (directly or via remoteMaxAtRef / routeSmartGate) with a ref that fails validation: empty, >128 chars, or containing characters outside [a-zA-Z0-9_./-].
Common situations: Same as the underlying validators: blank config values, pasted URLs, user-supplied refs with special characters; surfaced through doctor's read-only comparison of remote state.
Related errors
- ref cannot be empty
- --source-db and --output are required
- legacy SQLite source %q must be a regular file
- issue %s has invalid %s boolean
- ref too long
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cad9fda9fc2dc43f.
Report an issue: GitHub.