{"record":{"id":"78717ef5ccd05dbd","repo":"goharbor/harbor","slug":"bad-request-78717e","errorCode":"BAD_REQUEST","errorMessage":"the source repository %q contains only %d path components %v excepting the last one, but the destination namespace flattening level is %d","messagePattern":"the source repository %q contains only (.+?) path components (.+?) excepting the last one, but the destination namespace flattening level is (.+?)","errorType":"error_code","errorClass":"lib/errors.Error","httpStatus":400,"severity":"error","filePath":"src/controller/replication/flow/stage.go","lineNumber":205,"sourceCode":"// repository:a/b/c/image namespace:n replaceCount: 0 -> n/a/b/c/image\n// repository:a/b/c/image namespace:n replaceCount: 1 -> n/b/c/image\n// repository:a/b/c/image namespace:n replaceCount: 2 -> n/c/image\n// repository:a/b/c/image namespace:n replaceCount: 3 -> n/image\n// repository:a/b/c/image namespace:n replaceCount: 4 -> error\nfunc replaceNamespace(repository string, namespace string, replaceCount int8, dstRepoComponentPathType string) (string, error) {\n\tif len(namespace) == 0 {\n\t\treturn repository, nil\n\t}\n\n\tsrcRepoPathComponents := strings.Split(repository, \"/\")\n\tsrcLength := len(srcRepoPathComponents)\n\n\tvar dstRepoPrefix string\n\tswitch {\n\tcase replaceCount < 0: // legacy logic to keep backward compatibility\n\t\tdstRepoPrefix = namespace\n\tcase int(replaceCount) > srcLength-1: // invalid replace count\n\t\treturn \"\", errors.New(nil).WithCode(errors.BadRequestCode).\n\t\t\tWithMessagef(\"the source repository %q contains only %d path components %v excepting the last one, but the destination namespace flattening level is %d\",\n\t\t\t\trepository, srcLength-1, srcRepoPathComponents[:srcLength-1], replaceCount)\n\tdefault:\n\t\tdstRepoPrefix = namespace + \"/\" + strings.Join(srcRepoPathComponents[replaceCount:srcLength-1], \"/\")\n\t}\n\n\tname := srcRepoPathComponents[srcLength-1] // the last part of the repository path components, we'll keep it as the same with the source\n\tdstRepo := path.Join(dstRepoPrefix, name)\n\tdstRepoPathComponents := strings.Split(dstRepo, \"/\")\n\tdstLength := len(dstRepoPathComponents)\n\tswitch dstRepoComponentPathType {\n\tcase model.RepositoryPathComponentTypeOnlyTwo:\n\t\tif dstLength != 2 {\n\t\t\treturn \"\", errors.New(nil).WithCode(errors.BadRequestCode).WithMessagef(\"the destination repository %q contains %d path components %v, but the destination registry only supports 2\",\n\t\t\t\tdstRepo, dstLength, dstRepoPathComponents)\n\t\t}\n\tcase model.RepositoryPathComponentTypeAtLeastTwo:\n\t\tif dstLength < 2 {","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/controller/replication/flow/stage.go#L187-L223","documentation":"Thrown while computing the destination repository name in the replication flow (src/controller/replication/flow/stage.go:205). When a destination namespace is set, the source repository path is split on '/'; if the flattening level (replaceCount) exceeds the number of path components before the last one, there is nothing left to keep and the request is rejected as BAD_REQUEST (HTTP 400).","triggerScenarios":"Replicating a repository like 'library/ubuntu' (1 component before the last) with destination namespace set and flattening level >= 2; any rule where replaceCount > (number of '/'-separated source components minus 1).","commonSituations":"Copying a flattening level configured for deep paths (e.g. a/b/c/app) to shallow repos (a/app); flattening level raised after rule creation while replicating top-level repos; rule tuned against Docker Hub multi-level names then applied to 2-segment Harbor repos.","solutions":["Lower the flattening level so it is <= the component count of your shallowest source repo (for 2-segment repos use 0 or 1)","Leave the destination namespace empty - the source repository is then copied unchanged","Split the rule: one rule with high flattening for deep paths, another with low flattening for shallow ones"],"exampleFix":"# before\nsource repo: library/ubuntu\ndest namespace: backup\nflattening level: 2\n# 400: the source repository \"library/ubuntu\" contains only 1 path components ...\n\n# after\nsource repo: library/ubuntu\ndest namespace: backup\nflattening level: 1   # -> backup/ubuntu","handlingStrategy":"validation","validationCode":"func validateFlattening(srcRepo, namespace string, level int) error {\n    if namespace == \"\" { return nil }\n    comps := strings.Split(srcRepo, \"/\")\n    if level > len(comps)-1 {\n        return fmt.Errorf(\"flattening %d exceeds %d source components\", level, len(comps)-1)\n    }\n    return nil\n}","typeGuard":"func isFlatteningErr(err error) bool {\n    return errors.IsErr(err, errors.BadRequestCode) &&\n        strings.Contains(err.Error(), \"flattening level\")\n}","tryCatchPattern":"dst, err := flow.GetDestRepo(srcRepo, namespace, level)\nif err != nil {\n    if errors.IsErr(err, errors.BadRequestCode) {\n        // adjust level/namespace, do not retry unchanged\n    }\n    return err\n}","preventionTips":["Pre-compute component counts of the shallowest repo covered by each rule","Keep separate rules for shallow and deep source paths","Prefer flattening level 0/1 unless source paths are known to be deep"],"tags":["harbor","replication","path-rewriting","flattening","bad-request"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}