{"record":{"id":"f8cd6d3199ade40e","repo":"argoproj/argo-workflows","slug":"artifact-key-q-must-have-exactly-4-segments-uplo","errorCode":null,"errorMessage":"artifact key %q must have exactly 4 segments: uploads/{namespace}/{uuid}/{filename}","messagePattern":"artifact key %q must have exactly 4 segments: uploads/(.+?)/(.+?)/(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/artifactkey.go","lineNumber":37,"sourceCode":"// naming another user's upload under the same namespace still passes.\nfunc ValidateUploadedArtifactKey(namespace, key string) error {\n\tprefix := \"uploads/\" + namespace + \"/\"\n\tif !strings.HasPrefix(key, prefix) {\n\t\treturn fmt.Errorf(\"artifact key %q must start with %q\", key, prefix)\n\t}\n\tif strings.Contains(key, \"..\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not contain '..'\", key)\n\t}\n\tif strings.HasPrefix(key, \"/\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not be an absolute path\", key)\n\t}\n\tif path.Clean(key) != key {\n\t\treturn fmt.Errorf(\"artifact key %q is not in canonical form\", key)\n\t}\n\n\tparts := strings.Split(key, \"/\")\n\tif len(parts) != 4 {\n\t\treturn fmt.Errorf(\"artifact key %q must have exactly 4 segments: uploads/{namespace}/{uuid}/{filename}\", key)\n\t}\n\tif slices.Contains(parts, \"\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not contain empty segments\", key)\n\t}\n\n\tuuidSegment := parts[2]\n\tif _, err := uuid.Parse(uuidSegment); err != nil {\n\t\treturn fmt.Errorf(\"artifact key %q must have a valid UUID segment: %w\", key, err)\n\t}\n\n\tfilename := parts[3]\n\tif path.Base(filename) != filename {\n\t\treturn fmt.Errorf(\"artifact key %q must have a bare filename segment\", key)\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/utils/artifactkey.go#L19-L55","documentation":"After prefix validation, the key is split on '/' and must yield exactly 4 segments: uploads, {namespace}, {uuid}, {filename}. Fewer or more segments means the key does not match the upload endpoint's format and is rejected.","triggerScenarios":"Keys with nested subdirectories ('uploads/ns/uuid/dir/file.bin' = 5 segments), missing segments ('uploads/ns/file.bin' = 3), or keys not starting with 'uploads' that somehow passed other checks.","commonSituations":"Trying to store files in nested folders inside an upload; reusing keys from a different storage layout; appending query-like suffixes with slashes.","solutions":["Flatten the path to exactly uploads/{namespace}/{uuid}/{filename} — no subdirectories.","Generate a fresh UUID for the third segment and keep the filename as the sole final segment.","If subdirectories are needed, use a different artifact mechanism (e.g. artifact archives/raw outputs) rather than upload keys."],"exampleFix":"// before\nkey := \"uploads/my-ns/\" + id + \"/logs/2024/app.log\"\n// after\nkey := \"uploads/my-ns/\" + id + \"/app.log\"","handlingStrategy":"validation","validationCode":"parts := strings.Split(key, \"/\")\nif len(parts) != 4 || parts[0] != \"uploads\" {\n\treturn fmt.Errorf(\"key must be uploads/{namespace}/{uuid}/{filename}\")\n}","typeGuard":"func hasUploadKeyShape(key string) bool { return len(strings.Split(key, \"/\")) == 4 }","tryCatchPattern":"if err := utils.ValidateUploadedArtifactKey(ns, key); err != nil {\n\tif strings.Contains(err.Error(), \"exactly 4 segments\") {\n\t\tkey = path.Join(\"uploads\", ns, uuid.NewString(), filepath.Base(filename))\n\t}\n}","preventionTips":["Do not embed subdirectories in uploaded artifact keys — flatten filenames.","Generate a fresh UUID per upload for the third segment.","Unit-test key construction helpers against the 4-segment format."],"tags":["artifacts","validation"],"backgroundTag":"artifact-key-validation","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}