goharbor/harbor · error · lib/errors.Error
BAD_REQUEST
BAD_REQUEST
Error message
the source repository %q contains only %d path components %v excepting the last one, but the destination namespace flattening level is %d
What it means
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).
Source
Thrown at src/controller/replication/flow/stage.go:205
// repository:a/b/c/image namespace:n replaceCount: 0 -> n/a/b/c/image
// repository:a/b/c/image namespace:n replaceCount: 1 -> n/b/c/image
// repository:a/b/c/image namespace:n replaceCount: 2 -> n/c/image
// repository:a/b/c/image namespace:n replaceCount: 3 -> n/image
// repository:a/b/c/image namespace:n replaceCount: 4 -> error
func replaceNamespace(repository string, namespace string, replaceCount int8, dstRepoComponentPathType string) (string, error) {
if len(namespace) == 0 {
return repository, nil
}
srcRepoPathComponents := strings.Split(repository, "/")
srcLength := len(srcRepoPathComponents)
var dstRepoPrefix string
switch {
case replaceCount < 0: // legacy logic to keep backward compatibility
dstRepoPrefix = namespace
case int(replaceCount) > srcLength-1: // invalid replace count
return "", errors.New(nil).WithCode(errors.BadRequestCode).
WithMessagef("the source repository %q contains only %d path components %v excepting the last one, but the destination namespace flattening level is %d",
repository, srcLength-1, srcRepoPathComponents[:srcLength-1], replaceCount)
default:
dstRepoPrefix = namespace + "/" + strings.Join(srcRepoPathComponents[replaceCount:srcLength-1], "/")
}
name := srcRepoPathComponents[srcLength-1] // the last part of the repository path components, we'll keep it as the same with the source
dstRepo := path.Join(dstRepoPrefix, name)
dstRepoPathComponents := strings.Split(dstRepo, "/")
dstLength := len(dstRepoPathComponents)
switch dstRepoComponentPathType {
case model.RepositoryPathComponentTypeOnlyTwo:
if dstLength != 2 {
return "", errors.New(nil).WithCode(errors.BadRequestCode).WithMessagef("the destination repository %q contains %d path components %v, but the destination registry only supports 2",
dstRepo, dstLength, dstRepoPathComponents)
}
case model.RepositoryPathComponentTypeAtLeastTwo:
if dstLength < 2 {View on GitHub (pinned to 7b2fd08cc5)
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
Example fix
# before source repo: library/ubuntu dest namespace: backup flattening level: 2 # 400: the source repository "library/ubuntu" contains only 1 path components ... # after source repo: library/ubuntu dest namespace: backup flattening level: 1 # -> backup/ubuntu
Defensive patterns
Strategy: validation
Validate before calling
func validateFlattening(srcRepo, namespace string, level int) error {
if namespace == "" { return nil }
comps := strings.Split(srcRepo, "/")
if level > len(comps)-1 {
return fmt.Errorf("flattening %d exceeds %d source components", level, len(comps)-1)
}
return nil
} Type guard
func isFlatteningErr(err error) bool {
return errors.IsErr(err, errors.BadRequestCode) &&
strings.Contains(err.Error(), "flattening level")
} Try / catch
dst, err := flow.GetDestRepo(srcRepo, namespace, level)
if err != nil {
if errors.IsErr(err, errors.BadRequestCode) {
// adjust level/namespace, do not retry unchanged
}
return err
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/78717ef5ccd05dbd.
Report an issue: GitHub.