docker/cli · error
invalid SSH URL
Error message
invalid SSH URL: %w
What it means
Raised by ssh.ParseURL (ssh.go:16-22) when Go's net/url.Parse cannot parse the daemon URL at all. If the error is a *url.Error it is unwrapped to its cause before being wrapped as `invalid SSH URL: <err>`. This is a hard parse failure, distinct from semantic validation done later in newSpec.
Solutions
- Inspect the wrapped url.Parse error for the specific token/position at fault.
- Percent-encode any special characters in the URL (spaces as %20, etc.) or remove them.
- Quote the DOCKER_HOST value in the shell to avoid word-splitting: `export DOCKER_HOST='ssh://user@host'`.
- Test the URL with `python -c 'import urllib.parse; urllib.parse.urlparse(...)'` or by hand.
Example fix
# before — stray space / bad encoding export DOCKER_HOST='ssh://user @host%zz' # after — clean, encoded URL export DOCKER_HOST='ssh://user@host'
Defensive patterns
Strategy: validation
Validate before calling
// Reject URLs net/url cannot parse.
if _, err := url.Parse(daemonURL); err != nil {
return fmt.Errorf("DOCKER_HOST is not a valid URL: %w", err)
} Try / catch
spec, err := ssh.ParseURL(daemonURL)
if err != nil {
return fmt.Errorf("cannot parse ssh host: %w", err)
} Prevention
- Always quote DOCKER_HOST in shell assignments.
- Percent-encode special characters instead of leaving them raw.
- Validate URLs in CI configuration.
When it happens
Trigger: Calling ssh.ParseURL(daemonURL) where url.Parse(daemonURL) returns an error — e.g. control characters, an unencoded space, an invalid escape sequence (`%zz`), or a missing/illegal authority that the URL parser rejects.
Common situations: DOCKER_HOST contains an ssh URL with unencoded special characters, a malformed percent-encoding, or stray whitespace from shell quoting. Copy-pasting a URL with spaces or control characters triggers it.
Related errors
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/71dbb4bf4af96184.
Report an issue: GitHub.
Appendix: source
Thrown at cli/connhelper/ssh/ssh.go:22
import (
"errors"
"fmt"
"net/url"
"github.com/docker/cli/cli/connhelper/internal/syntax"
)
// ParseURL creates a [Spec] from the given ssh URL. It returns an error if
// the URL is using the wrong scheme, contains fragments, query-parameters,
// or contains a password.
func ParseURL(daemonURL string) (*Spec, error) {
u, err := url.Parse(daemonURL)
if err != nil {
var urlErr *url.Error
if errors.As(err, &urlErr) {
err = urlErr.Unwrap()
}
return nil, fmt.Errorf("invalid SSH URL: %w", err)
}
return NewSpec(u)
}
// NewSpec creates a [Spec] from the given ssh URL's properties. It returns
// an error if the URL is using the wrong scheme, contains fragments,
// query-parameters, or contains a password.
func NewSpec(sshURL *url.URL) (*Spec, error) {
s, err := newSpec(sshURL)
if err != nil {
return nil, fmt.Errorf("invalid SSH URL: %w", err)
}
return s, nil
}
func newSpec(u *url.URL) (*Spec, error) {
if u == nil {
return nil, errors.New("URL is nil")View on GitHub (pinned to 4f84911bfe)