twpayne/chezmoi · error

%s: empty path

Error message

%s: empty path

What it means

Each external entry must declare a target path (the key, or the TargetPath field override). If the resolved targetPath is the empty string, chezmoi cannot determine where to place the external and returns this error naming the source external file.

Source

Thrown at internal/chezmoi/sourcestate.go:1454

	if err != nil {
		return err
	}
	data, err := s.executeTemplate(sourceAbsPath)
	if err != nil {
		return fmt.Errorf("%s: %w", sourceAbsPath, err)
	}
	externals := make(map[string]External)
	if err := format.Unmarshal(data, &externals); err != nil {
		return fmt.Errorf("%s: %w", sourceAbsPath, err)
	}
	s.mutex.Lock()
	defer s.mutex.Unlock()
	for targetPath, external := range externals {
		if external.TargetPath != "" {
			targetPath = external.TargetPath
		}
		if targetPath == "" {
			return fmt.Errorf("%s: empty path", sourceAbsPath)
		}

		externalPath := path.Clean(targetPath)
		if strings.HasPrefix(externalPath, "/") || filepath.IsAbs(externalPath) {
			return fmt.Errorf("%s: %s: path is not relative", sourceAbsPath, targetPath)
		}
		switch relPath, err := filepath.Rel(".", externalPath); {
		case err != nil:
			return fmt.Errorf("%s: %s: %w", sourceAbsPath, targetPath, err)
		case relPath == ".":
			return fmt.Errorf("%s: %s: empty relative path", sourceAbsPath, targetPath)
		case relPath == "..", strings.HasPrefix(relPath, "../"),
			runtime.GOOS == "windows" && strings.HasPrefix(relPath, ".."+string(filepath.Separator)):
			return fmt.Errorf("%s: %s: relative path outside target directory", sourceAbsPath, targetPath)
		}
		targetRelPath := parentTargetSourceRelPath.JoinString(externalPath)
		external.sourceAbsPath = sourceAbsPath
		s.externals[targetRelPath] = append(s.externals[targetRelPath], &external)

View on GitHub (pinned to f901167e46)

Solutions

  1. Give each external entry a non-empty key path (the relative target path) in .chezmoiexternal.
  2. If using TargetPath, set it to a concrete relative path instead of an empty string.
  3. Fix the template that renders the key so it yields a real value.

Example fix

// before
[""]
type = "archive"
url = "https://example.com/x.tar.gz"
// after
[".local/bin/tool"]
type = "archive"
url = "https://example.com/x.tar.gz"
Defensive patterns

Strategy: validation

Validate before calling

// ensure every external entry has a non-empty target
for key, ext := range externals {
  target := ext.TargetPath
  if target == "" { target = key }
  if target == "" { return fmt.Errorf("external entry %q has empty path", key) }
}

Prevention

When it happens

Trigger: An external map entry with an empty-string key combined with no TargetPath field, or a TargetPath explicitly set to "" which fails the != "" check so the (empty) map key is used.

Common situations: Template rendering that evaluates a key/path to empty (e.g. {{ .missing }} yielding ""); copy-pasted external entries where the key was deleted; programmatic generation producing blank keys.

Related errors


AI-assisted analysis of twpayne/chezmoi@f901167e46 (2026-09-01). Data as JSON: /api/errors/f413a709f1fdb499. Report an issue: GitHub.