{"record":{"id":"642b5b255ac1b0ad","repo":"GoogleContainerTools/skaffold","slug":"cannot-parse-uri-q-w","errorCode":null,"errorMessage":"cannot parse URI %q: %w","messagePattern":"cannot parse URI %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/skaffold/gcs/client/native.go","lineNumber":136,"sourceCode":"\t\treturn err\n\t}\n\n\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:","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/GoogleContainerTools/skaffold/blob/a1189de023efc32d4b8e11f395acc678aa555011/pkg/skaffold/gcs/client/native.go#L118-L154","documentation":"parseGCSURI wraps url.Parse failures for a GCS URI argument. It means the string passed as src (DownloadRecursive) or dst (UploadFile) is not a syntactically valid URL, so the bucket and object path cannot be extracted. The original url.Error is wrapped with %w.","triggerScenarios":"Calling Native.DownloadRecursive or Native.UploadFile with a malformed URI string such as \"gs://bucket/[dir\" (invalid characters like unescaped brackets, spaces or control chars) instead of something like \"gs://bucket/dir/**\".","commonSituations":"Config values interpolated from shell variables producing spaces or unescaped characters; globs containing '[' or ']' character classes that url.Parse rejects; Windows paths with backslashes pasted in as dst.","solutions":["Print/inspect the wrapped error to find the invalid character and fix the URI string","Escape or remove URL-invalid characters (use url.PathEscape for interpolated segments, or encode brackets)","Ensure the URI has the form gs://bucket/path before passing it in","If using shell interpolation, quote variables: \"gs://bucket/${DIR}/**\" with DIR free of spaces/special chars"],"exampleFix":"// before\nn.DownloadRecursive(ctx, \"gs://bucket/[release]/config*\", dst) // url.Parse fails on '['\n// after\nuri := \"gs://bucket/\" + url.PathEscape(\"[release]\") + \"/config*\"\nif err := n.DownloadRecursive(ctx, uri, dst); err != nil { /* handle */ }","handlingStrategy":"validation","validationCode":"func validateGCSURISyntax(uri string) error {\n\tif _, err := url.Parse(uri); err != nil {\n\t\treturn fmt.Errorf(\"invalid URI %q: %w\", uri, err)\n\t}\n\treturn nil\n}\n// or sanitize interpolated values with url.PathEscape before building the URI","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Escape shell/user-provided path segments with url.PathEscape","Avoid unescaped brackets/spaces in glob patterns","Log the exact URI string when a parse error occurs","Centralize URI construction in one helper"],"tags":["gcs","uri-parsing","validation"],"backgroundTag":"invalid-uri","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"}