gastownhall/beads · error
invalid ref format: %s
Error message
invalid ref format: %s
What it means
validateMigrationRef checks the ref against ^[a-zA-Z0-9_./-]+$ and rejects anything else. Because the ref is interpolated into an AS OF SQL literal (bind params are not supported there), this allowlist is the SQL-injection guard; unsafe characters are rejected outright.
Source
Thrown at internal/storage/schema/migration_content_hashes.go:27
"github.com/steveyegge/beads/internal/storage/dberrors"
)
// validMigrationRefPattern matches the refs this package builds for AS OF reads
// (Dolt commit hashes or branch/remote-tracking names like
// "remotes/origin/main"). It mirrors issueops.ValidateRef but is kept local so
// the schema package — which sits below issueops — has no import-cycle risk.
var validMigrationRefPattern = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)
func validateMigrationRef(ref string) error {
if ref == "" {
return fmt.Errorf("ref cannot be empty")
}
if len(ref) > 128 {
return fmt.Errorf("ref too long")
}
if !validMigrationRefPattern.MatchString(ref) {
return fmt.Errorf("invalid ref format: %s", ref)
}
return nil
}
// 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
)View on GitHub (pinned to 71377f2769)
Solutions
- Normalize the ref to allowed characters (letters, digits, _, ., /, -) before calling.
- Strip whitespace and decorations; use the plain ref name (e.g. remotes/origin/main).
- Reject/escape at the CLI/config boundary so bad input never reaches the query.
- If the ref is user-controlled, validate with the same pattern before passing it in.
Example fix
// before
ref := strings.TrimSpace(userInput) // may contain quotes/spaces
schema.ReadMigrationContentHashes(ctx, db, ref)
// after
var refRe = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)
ref := strings.TrimSpace(userInput)
if !refRe.MatchString(ref) {
return fmt.Errorf("unsupported ref %q", ref)
}
schema.ReadMigrationContentHashes(ctx, db, ref) Defensive patterns
Strategy: validation
Validate before calling
var refRe = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)
if !refRe.MatchString(ref) { return fmt.Errorf("ref has unsupported characters: %q", ref) } Type guard
func validRef(ref string) bool {
return regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref)
} Prevention
- Sanitize any user/CLI-supplied ref with the same allowlist.
- Strip whitespace and shell artifacts before calling.
- Remember the ref is interpolated into SQL — never relax the allowlist.
When it happens
Trigger: Calling ReadMigrationContentHashes with refs containing quotes, spaces, colons, or other characters outside the allowlist — e.g. "origin/main feature" (space), "refs/heads/main:tip", a ref with a single quote, or user-supplied input passed through unchecked.
Common situations: User/CLI input passed directly as ref; shell interpolation leaving stray whitespace or quotes; attempting to pass SHA:size or other decorated syntax; injection attempts caught by the validator.
Related errors
- --addr %q binds beyond loopback, which requires --allow-non-
- remote URL cannot be empty
- remote URL contains control character at position %d (0x%02x
- remote URL must not start with a dash
- remote URL %q does not match any allowed pattern
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/1a4d82b4735c4789.
Report an issue: GitHub.