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

  1. Normalize the ref to allowed characters (letters, digits, _, ., /, -) before calling.
  2. Strip whitespace and decorations; use the plain ref name (e.g. remotes/origin/main).
  3. Reject/escape at the CLI/config boundary so bad input never reaches the query.
  4. 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

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


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