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
- Give each external entry a non-empty key path (the relative target path) in .chezmoiexternal.
- If using TargetPath, set it to a concrete relative path instead of an empty string.
- 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
- Always give external entries explicit non-empty keys
- Avoid templates in keys that can evaluate to empty
- Review generated external files for blank keys
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
- %s: %s: path is not relative
- %s: %s: %w
- %s: %w
- %s: cannot add chezmoi's config file to chezmoi, use a confi
- %s: cannot add chezmoi file to chezmoi (%s is protected)
AI-assisted analysis of twpayne/chezmoi@f901167e46 (2026-09-01).
Data as JSON: /api/errors/f413a709f1fdb499.
Report an issue: GitHub.