{"record":{"id":"93623ae5cdcf46bf","repo":"GoogleContainerTools/skaffold","slug":"uri-scheme-is-q-must-be-gs","errorCode":null,"errorMessage":"URI scheme is %q, must be 'gs'","messagePattern":"URI scheme is %q, must be 'gs'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/skaffold/gcs/client/native.go","lineNumber":139,"sourceCode":"\tdstObj := urinfo.ObjPath\n\tif isDirectory {\n\t\tdstObj, err = url.JoinPath(dstObj, filepath.Base(src))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn bucket.UploadObject(ctx, dstObj, f)\n}\n\nfunc (n *Native) parseGCSURI(uri string) (uriInfo, error) {\n\tvar gcsobj uriInfo\n\tu, err := url.Parse(uri)\n\tif err != nil {\n\t\treturn uriInfo{}, fmt.Errorf(\"cannot parse URI %q: %w\", uri, err)\n\t}\n\tif u.Scheme != \"gs\" {\n\t\treturn uriInfo{}, fmt.Errorf(\"URI scheme is %q, must be 'gs'\", u.Scheme)\n\t}\n\tif u.Host == \"\" {\n\t\treturn uriInfo{}, errors.New(\"bucket name is empty\")\n\t}\n\tgcsobj.Bucket = u.Host\n\t// If we do this with the url package it will scape the `?` character, breaking the glob.\n\tgcsobj.ObjPath = strings.TrimLeft(strings.ReplaceAll(uri, \"gs://\"+u.Host, \"\"), \"/\")\n\n\treturn gcsobj, nil\n}\n\nfunc (n *Native) filesToDownload(ctx context.Context, bucket bucketHandler, urinfo uriInfo) (map[string]string, error) {\n\turiToLocalPath := map[string]string{}\n\n\t// The exact match is with the original glob expression. This could be:\n\t// 1. a/b/c -> It will return the file `c` under a/b/ if it exists\n\t// 2. a/b/c* -> It will return any file under a/b/ that starts with c, e.g, c1, c-other, etc\n\t// 3. a/b/c** -> It will return any file that starts with 'c', and files inside any folder starting with 'c'. It is doing the recursion already","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/GoogleContainerTools/skaffold/blob/a1189de023efc32d4b8e11f395acc678aa555011/pkg/skaffold/gcs/client/native.go#L121-L157","documentation":"parseGCSURI rejects URIs whose scheme is not 'gs'. Both DownloadRecursive (src) and UploadFile (dst) require Google Cloud Storage URIs of the form gs://bucket/path. This error means a non-GCS location (local path, s3://, https://, or a bare path that yields an empty scheme) was passed.","triggerScenarios":"Calling DownloadRecursive with src like \"/local/dir\" or \"s3://bucket/key\", or UploadFile with a plain dst like \"./remote-cache\"; a bare hostname/path has empty scheme so u.Scheme != \"gs\" triggers this error.","commonSituations":"Config file mixes local and remote sources and the remote one is unset/empty; user pasted an https console URL instead of a gs:// URI; migrating from another tool that accepted s3-style URIs.","solutions":["Prefix the argument with gs:// and a bucket name, e.g. \"gs://my-bucket/path/**\"","Check the config for an empty/missing remote path and set it correctly","Use the GCS URI from `gcloud storage ls` output rather than a console web URL","Add an early check that strings.HasPrefix(arg, \"gs://\") in your calling code for a clearer failure"],"exampleFix":"// before\nn.UploadFile(ctx, \"./app.tar.gz\", \"artifacts/app.tar.gz\") // no scheme\n// after\nif !strings.HasPrefix(\"artifacts/app.tar.gz\", \"gs://\") {\n    return errors.New(\"destination must be a gs:// URI\")\n}\nif err := n.UploadFile(ctx, \"./app.tar.gz\", \"gs://artifacts/app.tar.gz\"); err != nil { /* handle */ }","handlingStrategy":"validation","validationCode":"func requireGCSURI(uri string) error {\n\tif !strings.HasPrefix(uri, \"gs://\") || len(strings.TrimPrefix(uri, \"gs://\")) == 0 {\n\t\treturn fmt.Errorf(\"expected gs://bucket/path, got %q\", uri)\n\t}\n\treturn nil\n}\n// call before DownloadRecursive/UploadFile","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate config values start with gs:// before invoking the library","Don't confuse console https URLs with gs:// URIs","Treat empty remote-path config as an error early in startup","Use `gcloud storage ls` to copy canonical URIs"],"tags":["gcs","uri-parsing","validation"],"backgroundTag":"invalid-uri-scheme","analyzedSha":"a1189de023efc32d4b8e11f395acc678aa555011","analyzedAt":"2026-09-05T12:09:27.064Z","contentChangedAt":"2026-09-05T12:09:27.064Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}