gastownhall/beads · error
invalid remote URL
Error message
invalid remote URL: %w
What it means
AddCLIRemote validates the remote URL with remotecache.ValidateRemoteURL before shelling out to `dolt remote add`. When the URL fails validation (empty value, unsupported scheme, embedded whitespace/control characters), the function aborts without invoking dolt and wraps the validator's error with "invalid remote URL". This guard exists so unvalidated strings never reach the dolt subprocess argv.
Solutions
- Log or print the exact url value passed to EnsureCLIRemote/AddCLIRemote to see what is actually being validated
- Correct the remote URL in your beads/dolt config to a full, valid http(s) URL
- Trim surrounding whitespace and control characters from the configured value
- If the URL looks valid, review remotecache.ValidateRemoteURL — the wrapped error states the specific rule violated
Example fix
// before EnsureCLIRemote(dbPath, "origin", "git@github.com:me/beads.git") // scp-style URL rejected // after EnsureCLIRemote(dbPath, "origin", "https://github.com/me/beads.git")
Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(remoteURL)
if err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("remote URL must be absolute with a host, got %q", remoteURL)
}
if strings.TrimSpace(remoteURL) != remoteURL {
return fmt.Errorf("remote URL has surrounding whitespace")
} Type guard
func isValidRemoteURL(u string) bool {
parsed, err := url.Parse(u)
return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https") && parsed.Host != "" && u == strings.TrimSpace(u)
} Prevention
- Store remote URLs as full http(s) URLs in config, never scp-style git shorthand
- Trim whitespace when loading URLs from env vars, flags, or config files
- Validate config once at startup so bad URLs fail before any sync command runs
When it happens
Trigger: Calling AddCLIRemote or EnsureCLIRemote with a url that fails ValidateRemoteURL: an empty string, a value without a supported scheme (e.g. "htp://..." or a bare "github.com/me/repo"), or a URL containing spaces or control characters.
Common situations: Typo'd remote URL in beads sync config; trailing whitespace from copy-pasting a URL; passing an scp-style git URL (git@host:path) where dolt expects http(s); an unset config key interpolated as an empty string.
Related errors
- invalid --dolt-auto-commit=
- invalid remote name
- add replacement CLI remote failed; previous URL
- --addr binds beyond loopback, which requires…
- --addr : host must be a numeric IP literal, not a name —…
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/deae8cb5db0c6bb9.
Report an issue: GitHub.
Appendix: source
Thrown at internal/storage/doltutil/remotes.go:162
func RemoteURLsMatch(got, want string) bool {
if got == "" || want == "" {
return got == want
}
if got == want || doltremote.Normalize(got) == doltremote.Normalize(want) {
return true
}
return false
}
// AddCLIRemote adds a remote at the filesystem level via dolt CLI.
// Remote mutation should normally go through SQL; this is reserved for the
// local CLI mirror required by subprocess push/pull/fetch routing.
func AddCLIRemote(dbPath, name, url string) error {
if err := remotecache.ValidateRemoteName(name); err != nil {
return fmt.Errorf("invalid remote name: %w", err)
}
if err := remotecache.ValidateRemoteURL(url); err != nil {
return fmt.Errorf("invalid remote URL: %w", err)
}
cmd := exec.Command("dolt", "remote", "add", name, url) // #nosec G204 -- validated argv
cmd.Dir = dbPath
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("dolt remote add failed: %s: %w", strings.TrimSpace(string(out)), err)
}
return nil
}
// RemoveCLIRemote removes a remote at the filesystem level via dolt CLI.
func RemoveCLIRemote(dbPath, name string) error {
if err := remotecache.ValidateRemoteName(name); err != nil {
return fmt.Errorf("invalid remote name: %w", err)
}
cmd := exec.Command("dolt", "remote", "remove", name) // #nosec G204 -- validated argv
cmd.Dir = dbPath
out, err := cmd.CombinedOutput()View on GitHub (pinned to 71377f2769)