gastownhall/beads · error

invalid ref: %w

Error message

invalid ref: %w

What it means

LocalIsStrictAncestorOf reports whether the given ref is a strict descendant of the local HEAD. Before querying, it validates the ref with issueops.ValidateRef, which requires a non-empty ref of at most 128 chars matching [a-zA-Z0-9_./-]+. The ref fails this allowlist, so the function wraps the validation error with "invalid ref: %w" and returns false.

Source

Thrown at internal/storage/versioncontrolops/fastforward.go:28

// This file holds additive driver primitives for a fast-forward "smart
// migrate" gate: checking whether local HEAD is a strict ancestor of a
// cached ref, checking the working set is clean (ignoring wisp tables), and
// performing the actual fast-forward-only adopt. Nothing in this package or
// elsewhere calls these yet — they are wired into the smart migrate gate in
// a later change.

// LocalIsStrictAncestorOf reports whether local HEAD is a STRICT ancestor of
// ref in the Dolt commit graph: local has zero commits that ref lacks
// (ahead == 0) and at least one commit that local lacks (behind >= 1). A
// local HEAD equal to ref (ahead == 0, behind == 0) is NOT a strict
// ancestor, and returns false.
//
// ref must already be present locally (e.g. a cached remote-tracking ref
// such as "origin/main" after a fetch); this performs no fetch of its own.
func LocalIsStrictAncestorOf(ctx context.Context, db DBConn, ref string) (bool, error) {
	if err := issueops.ValidateRef(ref); err != nil {
		return false, fmt.Errorf("invalid ref: %w", err)
	}

	// Dolt's AS OF requires a literal ref, not a bind parameter; ref was
	// validated above via the shared allowlist regex, mirroring the same
	// ahead/behind pattern used by EmbeddedDoltStore.SyncStatus
	// (internal/storage/embeddeddolt/federation.go).
	//nolint:gosec // G201: ref validated by ValidateRef above — AS OF requires a literal
	query := fmt.Sprintf(`
		SELECT
			(SELECT COUNT(*) FROM dolt_log WHERE commit_hash NOT IN
				(SELECT commit_hash FROM dolt_log AS OF '%s')) AS ahead,
			(SELECT COUNT(*) FROM dolt_log AS OF '%s' WHERE commit_hash NOT IN
				(SELECT commit_hash FROM dolt_log)) AS behind
	`, ref, ref)

	var ahead, behind int
	if err := db.QueryRowContext(ctx, query).Scan(&ahead, &behind); err != nil {
		return false, fmt.Errorf("compare local HEAD to %s: %w", ref, err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Trim whitespace and re-check the ref string; it must be non-empty, <=128 chars, and match ^[a-zA-Z0-9_./-]+$.
  2. Call issueops.ValidateRef(ref) yourself before invoking LocalIsStrictAncestorOf to get the exact reason.
  3. If the ref should be a commit hash, confirm you are passing the 32-hex-char hash and not a message or label.
  4. If you need the remote value, ensure the ref is a cached remote-tracking name like 'origin/main' after a fetch.

Example fix

// before
isAncestor, err := versioncontrolops.LocalIsStrictAncestorOf(ctx, db, ref)
// after
ref = strings.TrimSpace(ref)
if err := issueops.ValidateRef(ref); err != nil {
    return fmt.Errorf("refusing call: %w", err)
}
isAncestor, err := versioncontrolops.LocalIsStrictAncestorOf(ctx, db, ref)
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

func isSafeRef(s string) bool {
    if len(s) == 0 || len(s) > 128 { return false }
    for _, r := range s {
        ok := r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' ||
            r == '_' || r == '.' || r == '/' || r == '-'
        if !ok { return false }
    }
    return true
}

Try / catch

ok, err := versioncontrolops.LocalIsStrictAncestorOf(ctx, db, ref)
if err != nil {
    var validationFailed bool = strings.Contains(err.Error(), "invalid ref")
    if validationFailed {
        return fmt.Errorf("caller bug: bad ref %q: %w", ref, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling LocalIsStrictAncestorOf(ctx, db, ref) with an empty string, a ref longer than 128 chars, or a ref containing characters outside [a-zA-Z0-9_./-] (e.g. spaces, 'origin..main', shell metacharacters, 'HEAD~1' style suffixes with disallowed chars).

Common situations: Passing a user-supplied ref name straight into the API without trimming; constructing a ref by string concatenation that accidentally includes whitespace or newlines; passing a full 'refs/heads/main' style path with characters like ':' or an empty variable due to a failed config lookup.

Related errors


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