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
- Trim whitespace and re-check the ref string; it must be non-empty, <=128 chars, and match ^[a-zA-Z0-9_./-]+$.
- Call issueops.ValidateRef(ref) yourself before invoking LocalIsStrictAncestorOf to get the exact reason.
- If the ref should be a commit hash, confirm you are passing the 32-hex-char hash and not a message or label.
- 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
- Always run issueops.ValidateRef on refs sourced from user input before any versioncontrolops call.
- Trim whitespace/newlines when capturing ref names from command output or config.
- Use well-known forms only: branch names, 'origin/*' remote-tracking refs, or 32-hex commit hashes.
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
- invalid --dolt-auto-commit=%q (valid: off, on, batch)
- remote target %s is non-empty but is neither a bare git repo
- ExternalDoltConfig: Port %d out of range [1, 65535]
- dolt metadata path %s is not a directory
- loop %q: count must be positive
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4b17cf3335bc4484.
Report an issue: GitHub.