sipeed/picoclaw · error

source and target must be absolute paths

Error message

source and target must be absolute paths

What it means

After filepath.Clean (and defaulting target to source when target is empty), ValidateExposePaths requires both source and target to be absolute paths. Relative paths — including "~/..." which Clean does not expand — make the exposure rule ambiguous against the child's redirected filesystem, so preflight rejects them.

Source

Thrown at pkg/isolation/runtime.go:181

func ValidateExposePaths(items []config.ExposePath) error {
	seen := map[string]struct{}{}
	for _, item := range items {
		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{

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Expand ~ yourself and write the full absolute path (e.g. /home/me/project)
  2. Give every entry an explicit absolute target when it differs from the source
  3. On Linux, ensure paths start with /; on Windows, use drive-letter or UNC absolute paths
  4. If generating config programmatically, run filepath.Abs on values before serializing

Example fix

# before
isolation:
  expose_paths:
    - source: ~/project
      mode: ro

# after
isolation:
  expose_paths:
    - source: /home/me/project
      mode: ro
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range cfg.Isolation.ExposePaths {
    src := p.Source
    if strings.HasPrefix(src, "~" + string(os.PathSeparator)) || src == "~" {
        if home, err := os.UserHomeDir(); err == nil {
            src = filepath.Join(home, strings.TrimPrefix(src, "~"))
        }
    }
    if !filepath.IsAbs(src) {
        abs, err := filepath.Abs(src)
        if err != nil {
            return fmt.Errorf("cannot make expose source absolute: %s", p.Source)
        }
        src = abs
    }
    p.Source = src
    // repeat for target, then validate
}

Prevention

When it happens

Trigger: source: ~/project (tilde is a relative path to filepath.IsAbs); source: ./data or data; target: workspace (relative); a Windows-style path like C:\data evaluated on Linux where filepath.IsAbs expects a leading /.

Common situations: Shell-style habits (~) carried into config; configs moved between Windows and Linux hosts where the absoluteness rules differ; relative paths that worked in a non-isolated mode and were never made absolute.

Related errors


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