gastownhall/beads · error

ref cannot be empty

Error message

ref cannot be empty

What it means

validateMigrationRef rejects an empty ref string. In ReadMigrationContentHashes an empty ref means "read at HEAD", so callers hitting this validator via the ref path passed an explicitly empty string where a concrete ref (e.g. "remotes/origin/main") was required. The validator mirrors issueops.ValidateRef locally to avoid an import cycle.

Source

Thrown at internal/storage/schema/migration_content_hashes.go:21

import (
	"context"
	"database/sql"
	"fmt"
	"regexp"
	"strings"

	"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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Supply a concrete ref such as "remotes/origin/main" or a branch/tag name.
  2. If HEAD is intended, keep ref == "" only at the top-level ReadMigrationContentHashes entry that skips validation, not the AS OF path.
  3. Validate the ref source (config/flag) before calling; fail fast with a clear message.
  4. Default the ref from git remotes when unset.

Example fix

// before
hashes, err := schema.ReadMigrationContentHashes(ctx, db, cfg.CompareRef) // ""
// after
ref := cfg.CompareRef
if ref == "" {
    ref = "remotes/origin/main"
}
hashes, err := schema.ReadMigrationContentHashes(ctx, db, ref)
Defensive patterns

Strategy: validation

Validate before calling

if ref == "" { return errors.New("a migration ref is required") }
// proceed to ReadMigrationContentHashes(ctx, db, ref)

Type guard

func validRef(ref string) bool {
    return ref != "" && len(ref) <= 128 &&
        regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref)
}

Prevention

When it happens

Trigger: Calling ReadMigrationContentHashes with a ref that is non-empty by contract but blank in practice — e.g. an unset config value, an empty branch variable, or a caller that trimmed the ref to "" before invoking the ref (AS OF) code path.

Common situations: Config file or environment variable for the comparison ref missing/blank; a script interpolating an empty variable into the ref argument; upstream function returning "" to signal HEAD while the caller intended a historical read.

Related errors


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