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

  1. Lower the flattening level so it is <= the component count of your shallowest source repo (for 2-segment repos use 0 or 1)
  2. Leave the destination namespace empty - the source repository is then copied unchanged
  3. 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

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.