{"record":{"id":"e84abc59b48b108a","repo":"gastownhall/beads","slug":"s-url-must-include-a-bucket-name","errorCode":null,"errorMessage":"%s:// URL must include a bucket name","messagePattern":"(.+?):// URL must include a bucket name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/remotecache/url.go","lineNumber":157,"sourceCode":"\t\tif p != \"\" {\n\t\t\tcombined = host + \"/\" + p\n\t\t}\n\t\tparts := strings.Split(combined, \"/\")\n\t\tif len(parts) < 2 || parts[0] == \"\" || parts[1] == \"\" {\n\t\t\treturn fmt.Errorf(\"dolthub:// URL must have org/repo format (e.g., dolthub://myorg/myrepo)\")\n\t\t}\n\tcase \"https\", \"http\", \"git+https\", \"git+http\":\n\t\tif parsed.Host == \"\" {\n\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}","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/url.go#L139-L175","documentation":"For s3, aws, and gs URLs the hostname portion is the bucket name (s3://bucket/path). If parsed.Host is empty, no bucket was specified and the URL cannot address cloud storage.","triggerScenarios":"ValidateRemoteURL with \"s3://\", \"s3:///prefix/path\", \"gs://\", or \"aws:///bucket\" — bucket name missing or misplaced in the path.","commonSituations":"Env-var substitution leaving BUCKET empty, putting the bucket in the path (s3:///bucket/key), or forgetting the bucket when hand-writing cloud remote URLs.","solutions":["Put the bucket in the host position: \"s3://my-bucket/path\"","Check that the BUCKET env/config variable is populated","Verify the URL is not s3:///bucket/path — the third slash means an empty host"],"exampleFix":"// before\nremote := \"s3:///my-bucket/db\"\n// after\nremote := \"s3://my-bucket/db\"","handlingStrategy":"validation","validationCode":"func validCloudStoreURL(u string) bool {\n\tscheme, rest, ok := strings.Cut(u, \"://\")\n\tif !ok { return false }\n\tswitch scheme { case \"s3\", \"aws\", \"gs\": default: return false }\n\thost := rest\n\tif i := strings.Index(rest, \"/\"); i >= 0 { host = rest[:i] }\n\treturn host != \"\"\n}","typeGuard":"func cloudURLWithBucket(s string) (bucket string, ok bool) {\n\ti := strings.Index(s, \"://\")\n\tif i < 0 { return \"\", false }\n\trest := s[i+3:]\n\tif j := strings.Index(rest, \"/\"); j >= 0 { rest = rest[:j] }\n\treturn rest, rest != \"\"\n}","tryCatchPattern":"if err := remotecache.ValidateRemoteURL(u); err != nil {\n\tif strings.Contains(err.Error(), \"must include a bucket name\") {\n\t\treturn fmt.Errorf(\"cloud remote %q missing bucket; use s3://bucket/path\", u)\n\t}\n}","preventionTips":["Format cloud URLs as fmt.Sprintf(\"s3://%s/%s\", bucket, key) and assert bucket != \"\"","Never hand-write three slashes after the scheme","Check BUCKET config at startup"],"tags":["url-validation","s3","cloud-storage","bucket"],"backgroundTag":"invalid-remote-url-format","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}