{"record":{"id":"511b4b8a510813a2","repo":"gastownhall/beads","slug":"oci-url-must-include-a-namespace-or-bucket-host","errorCode":null,"errorMessage":"oci:// URL must include a namespace or bucket host","messagePattern":"oci:// URL must include a namespace or bucket host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/remotecache/url.go","lineNumber":166,"sourceCode":"\t\t\treturn fmt.Errorf(\"%s:// URL must include a hostname\", scheme)\n\t\t}\n\tcase \"ssh\", \"git\", \"git+ssh\":\n\t\tif parsed.Host == \"\" {\n\t\t\treturn fmt.Errorf(\"%s:// URL must include a hostname\", scheme)\n\t\t}\n\tcase \"s3\", \"aws\", \"gs\":\n\t\t// s3://bucket/path, aws://bucket/path, gs://bucket/path — host is the bucket\n\t\tif parsed.Host == \"\" {\n\t\t\treturn fmt.Errorf(\"%s:// URL must include a bucket name\", scheme)\n\t\t}\n\tcase \"az\":\n\t\t// az://account.blob.core.windows.net/container/path\n\t\tif parsed.Host == \"\" {\n\t\t\treturn fmt.Errorf(\"az:// URL must include a storage account hostname\")\n\t\t}\n\tcase \"oci\":\n\t\tif parsed.Host == \"\" {\n\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\")","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/url.go#L148-L184","documentation":"oci:// URLs must include a namespace or bucket host (e.g. oci://namespace/bucket/path). An empty parsed.Host means the registry namespace/bucket portion is missing, so the object store location is unresolvable.","triggerScenarios":"ValidateRemoteURL with \"oci://\", \"oci:///bucket/path\", or a URL missing the namespace host segment.","commonSituations":"OCI remotes built from templates where the NAMESPACE variable was empty, or copying only the object path from a cloud console URL.","solutions":["Add the namespace/bucket host: \"oci://my-namespace/my-bucket/path\"","Check OCI_NAMESPACE / bucket config substitution is non-empty","Match the object-storage layout used by your OCI provider (namespace first, then bucket)"],"exampleFix":"// before\nremote := \"oci:///my-bucket/db\"\n// after\nremote := \"oci://my-namespace/my-bucket/db\"","handlingStrategy":"validation","validationCode":"func validOCIURL(u string) bool {\n\trest, ok := strings.CutPrefix(u, \"oci://\")\n\tif !ok { return false }\n\thost := rest\n\tif i := strings.Index(rest, \"/\"); i >= 0 { host = rest[:i] }\n\treturn host != \"\"\n}","typeGuard":"func ociURLWithNamespace(s string) (ns string, ok bool) {\n\trest, has := strings.CutPrefix(s, \"oci://\")\n\tif !has { return \"\", false }\n\tif i := strings.Index(rest, \"/\"); i >= 0 { rest = rest[:i] }\n\treturn rest, rest != \"\"\n}","tryCatchPattern":"if err := remotecache.ValidateRemoteURL(u); err != nil {\n\tif strings.Contains(err.Error(), \"namespace or bucket host\") {\n\t\treturn fmt.Errorf(\"oci remote %q must be oci://namespace/bucket/path\", u)\n\t}\n}","preventionTips":["Format as fmt.Sprintf(\"oci://%s/%s/%s\", namespace, bucket, path) with namespace checked non-empty","Check OCI namespace config at startup","Add a builder function instead of string concatenation at call sites"],"tags":["url-validation","oci","cloud-storage"],"backgroundTag":"invalid-remote-url-format","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}