{"record":{"id":"145c16ba5a2709c7","repo":"gastownhall/beads","slug":"scp-style-url-must-be-in-user-host-path-format","errorCode":null,"errorMessage":"SCP-style URL must be in user@host:path format","messagePattern":"SCP-style URL must be in user@host:path format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/remotecache/url.go","lineNumber":184,"sourceCode":"\t\t\treturn fmt.Errorf(\"oci:// URL must include a namespace or bucket host\")\n\t\t}\n\tcase \"file\":\n\t\t// file:// is allowed with any path\n\tcase \"git+file\":\n\t\t// git+file:// is Dolt's normalized form for local git remotes.\n\t}\n\n\treturn nil\n}\n\n// validateSCPURL validates an SCP-style URL (user@host:path)\nfunc validateSCPURL(rawURL string) error {\n\t// Already matched gitSSHPattern, so structure is valid.\n\t// Extract host and verify no control chars (already checked above).\n\tatIdx := strings.Index(rawURL, \"@\")\n\tcolonIdx := strings.Index(rawURL[atIdx:], \":\")\n\tif atIdx < 0 || colonIdx < 0 {\n\t\treturn fmt.Errorf(\"SCP-style URL must be in user@host:path format\")\n\t}\n\treturn nil\n}\n\n// ValidateRemoteName checks that a remote name is safe for use as a Dolt\n// remote identifier. Names must start with a letter and contain only\n// alphanumeric characters, hyphens, and underscores. Max 64 characters.\nfunc ValidateRemoteName(name string) error {\n\tif name == \"\" {\n\t\treturn fmt.Errorf(\"remote name cannot be empty\")\n\t}\n\tif len(name) > 64 {\n\t\treturn fmt.Errorf(\"remote name too long (max 64 characters)\")\n\t}\n\tif strings.HasPrefix(name, \"-\") {\n\t\treturn fmt.Errorf(\"remote name must not start with a dash\")\n\t}\n\tif !validRemoteNameRegex.MatchString(name) {","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/url.go#L166-L202","documentation":"SCP-style remote URLs must look like user@host:path. This error guards validateSCPURL's invariants: it runs only after gitSSHPattern matched, so in practice the pattern already guarantees \"@\" and \":\" are present and this branch is defensive/unreachable for normal input — but if reached, the string lacked an @ or a colon after it.","triggerScenarios":"ValidateRemoteURL with an SCP-shaped string missing \"@\" or missing \":\" after the host. Because gitSSHPattern requires both, this is only reachable if the pattern and this check diverge (e.g. future pattern changes).","commonSituations":"Typing a git remote without the colon separator (\"git@example.com/path\") or without the user part, expecting it to be accepted as SSH.","solutions":["Use the full SCP form: \"git@github.com:org/repo.git\"","If you prefer scheme syntax, use \"ssh://git@github.com/org/repo.git\" instead","Remember the separator after the host is a colon, not a slash"],"exampleFix":"// before\nremote := \"git@github.com/org/repo\"\n// after\nremote := \"git@github.com:org/repo\"","handlingStrategy":"validation","validationCode":"var scpRe = regexp.MustCompile(`^[^@\\s]+@[^@\\s:]+:.+$`)\nfunc validSCPForm(u string) bool { return scpRe.MatchString(u) }\n// check before passing to ValidateRemoteURL","typeGuard":"func isSCPStyle(s string) bool {\n\treturn regexp.MustCompile(`^[a-zA-Z0-9._-]+@[a-zA-Z0-9][a-zA-Z0-9._-]*:[^\\x00-\\x1f\\x7f]+$`).MatchString(s)\n}","tryCatchPattern":"if err := remotecache.ValidateRemoteURL(u); err != nil {\n\tif strings.Contains(err.Error(), \"user@host:path\") {\n\t\treturn fmt.Errorf(\"remote %q must be git@host:path or ssh://host/path\", u)\n\t}\n}","preventionTips":["Always use the colon separator after the host in SCP-style remotes","Prefer ssh:// scheme URLs, which are validated more predictably","Run the gitSSHPattern regex on user input before submitting"],"tags":["url-validation","ssh","scp","remote-url"],"backgroundTag":"invalid-remote-url-format","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}