twpayne/chezmoi · error

%s: %w

Error message

%s: %w

What it means

When reading a .chezmoiexternal file (or external entry source), chezmoi first executes it as a template, then wraps any template execution error with the source file path for context. The underlying %w error is the template failure (syntax error, missing variable, failing function).

Source

Thrown at internal/chezmoi/sourcestate.go:1441

// addExternal adds external source entries to s.
func (s *SourceState) addExternal(sourceAbsPath, parentAbsPath AbsPath) error {
	parentRelPath, err := parentAbsPath.TrimDirPrefix(s.sourceDirAbsPath)
	if err != nil {
		return err
	}
	parentSourceRelPath := NewSourceRelDirPath(parentRelPath.String())
	parentTargetSourceRelPath, err := parentSourceRelPath.TargetRelPath(s.encryption.EncryptedSuffix())
	if err != nil {
		return err
	}

	format, err := FormatFromAbsPath(sourceAbsPath.TrimSuffix(TemplateSuffix))
	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)

View on GitHub (pinned to f901167e46)

Solutions

  1. Run 'chezmoi execute-template < <file>' or inspect the wrapped error message to find the failing template expression and fix it.
  2. Remove unnecessary {{ }} constructs from the external file (static content doesn't need templating).
  3. Verify template variables exist for your environment ('chezmoi data').

Example fix

// before (.chezmoiexternal.toml.tmpl)
url = "https://example.com/{{ .undefinedVar }}"
// after
url = "https://example.com/{{ .chezmoi.hostname }}/asset"
Defensive patterns

Strategy: try-catch

Try / catch

err := sourceState.readExternal(...)
var pathErr *fmt.WrapError // or match on prefix
if err != nil {
  if strings.Contains(err.Error(), ": ") && templateFailed(err) {
    // log file path from prefix, surface inner template error to user
  }
}

Prevention

When it happens

Trigger: A .chezmoiexternal.toml/.json/.yaml containing template syntax ({{ ... }}) that fails to execute, e.g. referencing undefined .chezmoi* variables or calling a template function with bad args.

Common situations: Template typos like {{ .chezmoi.hostname }} in files that don't get template context; copy-pasted template snippets into externals; version change removing a template function.

Related errors


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