twpayne/chezmoi · error
%s: %s: path is not relative
Error message
%s: %s: path is not relative
What it means
External target paths must be relative to the target (home) directory. After cleaning, if the path starts with '/' or is absolute (filepath.IsAbs, relevant on Windows e.g. C:\...), chezmoi rejects it because externals may only write inside the target directory.
Source
Thrown at internal/chezmoi/sourcestate.go:1459
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)
}
return nil
}
// addExternalDir adds all externals in externalsDirAbsPath to s.View on GitHub (pinned to f901167e46)
Solutions
- Change the external target path to a path relative to your home directory (e.g. '.local/bin/tool', 'dot_config/tool').
- If the external must land outside home, unpack via a script external (type = 'script') that installs to the absolute location itself.
- Fix the template variable that renders the absolute path.
Example fix
// before ["/opt/tool"] type = "archive" url = "https://example.com/tool.tgz" // after [".local/opt/tool"] type = "archive" url = "https://example.com/tool.tgz"
Defensive patterns
Strategy: validation
Validate before calling
target := keyOrTargetPath
if strings.HasPrefix(target, "/") || filepath.IsAbs(target) {
return fmt.Errorf("external path %q must be relative to home", target)
} Prevention
- Express all external targets relative to $HOME
- Use script-type externals for out-of-home installs
- Cross-check Windows configs for drive-letter paths
When it happens
Trigger: An external entry key or TargetPath like '/opt/tool' or 'C:/tools/tool' in .chezmoiexternal, or a template rendering to an absolute path.
Common situations: Users copying install paths from documentation (which often use absolute paths) into external configs; cross-platform externals using Windows absolute paths.
Related errors
- %s: empty path
- %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/43ba5391a3f03eca.
Report an issue: GitHub.