sipeed/picoclaw · error

duplicate expose_path target: %s

Error message

duplicate expose_path target: %s

What it means

ValidateExposePaths tracks cleaned target paths in a set; a second expose_paths entry resolving to the same target (explicitly or by defaulting to its source) is rejected. Duplicate mount targets would make the mapping order-dependent, so preflight fails with the colliding path.

Source

Thrown at pkg/isolation/runtime.go:184

		if item.Source == "" {
			return fmt.Errorf("source is required")
		}
		if item.Mode != "ro" && item.Mode != "rw" {
			return fmt.Errorf("invalid expose_paths mode: %s", item.Mode)
		}

		source := filepath.Clean(item.Source)
		target := item.Target
		if target == "" {
			target = source
		}
		target = filepath.Clean(target)

		if !filepath.IsAbs(source) || !filepath.IsAbs(target) {
			return fmt.Errorf("source and target must be absolute paths")
		}
		if _, ok := seen[target]; ok {
			return fmt.Errorf("duplicate expose_path target: %s", target)
		}
		seen[target] = struct{}{}
	}
	return nil
}

// NormalizeExposePath fills implicit defaults and cleans path values so merge
// and validation logic can work with canonical paths.
func NormalizeExposePath(item config.ExposePath) config.ExposePath {
	source := filepath.Clean(item.Source)
	target := item.Target
	if target == "" {
		target = source
	}
	return config.ExposePath{
		Source: source,
		Target: filepath.Clean(target),
		Mode:   item.Mode,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Remove or merge the duplicate rules so each target appears once
  2. If two sources must both be visible, give them distinct targets (e.g. /workspace/a and /workspace/b)
  3. Normalize paths first: remember comparison happens on filepath.Clean values, so cosmetic differences do not make entries distinct

Example fix

# before
isolation:
  expose_paths:
    - {source: /home/me/project, mode: rw}
    - {source: /home/me/project, target: /project, mode: ro}

# after
isolation:
  expose_paths:
    - {source: /home/me/project, mode: rw}
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]struct{}{}
for _, p := range cfg.Isolation.ExposePaths {
    target := p.Target
    if target == "" {
        target = p.Source
    }
    target = filepath.Clean(target)
    if _, dup := seen[target]; dup {
        return fmt.Errorf("duplicate expose target %s — merge or split targets", target)
    }
    seen[target] = struct{}{}
}

Prevention

When it happens

Trigger: Two entries with the same target; or two entries whose target keys are empty so both default to their (identical) source paths; entries that differ in textual form but Clean to the same path (e.g. /a/b and /a//b/).

Common situations: Appending a new rule without noticing an existing one mounts the same location; YAML anchors or copy-paste duplication; merging config layers that each add a rule for the same directory.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/d56efa5a990dacf3. Report an issue: GitHub.