gastownhall/beads · error

invalid remote URL: %w

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.

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)

Solutions

  1. Log or print the exact url value passed to EnsureCLIRemote/AddCLIRemote to see what is actually being validated
  2. Correct the remote URL in your beads/dolt config to a full, valid http(s) URL
  3. Trim surrounding whitespace and control characters from the configured value
  4. 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

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


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