twpayne/chezmoi · error

%s: %q: %w

Error message

%s: %q: %w

What it means

This wrapped error originates in chezmoi's source state handling (around sourcestate.go:2977, in the script-related error paths) and follows the format '%s: %q: %w': the first %s is the context (usually the target path of the source entry), %q quotes the specific item — commonly a script name or attribute being processed — and %w embeds the underlying error, preserving it for errors.Is/As unwrapping. It is raised when an operation on a source-state entry (such as reading, hashing, or evaluating a script) fails due to an underlying I/O, permission, or parse error. Diagnose it by reading the wrapped error at the end of the message: for example, a 'no such file or directory' means the referenced file is missing from the source state, while a permission error means chezmoi cannot read the entry. Fix the underlying condition (restore the missing file, correct permissions, or repair malformed frontmatter) in ~/.local/share/chezmoi.

Solutions

  1. Make the file contain exactly one valid semantic version string (e.g. 2.1.0), trimmed of extra text
  2. Remove surrounding prose/quotes/constraint operators; use a bare MAJOR.MINOR.PATCH
  3. Include the patch segment — '1.2' is invalid, '1.2.0' is valid

Example fix

// before (version file contents)
1.2
// after
2.1.0
Defensive patterns

Strategy: validation

Validate before calling

// validate the version file is strict semver before relying on it
re := regexp.MustCompile(`^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$`)
data, _ := os.ReadFile(versionFile)
v := strings.TrimSpace(string(data))
if !re.MatchString(v) { return fmt.Errorf("%s: not strict semver: %q", versionFile, v) }

Type guard

func isStrictSemver(s string) bool {
  _, err := semver.NewVersion(strings.TrimSpace(s))
  return err == nil
}

Try / catch

// catch and surface the offending contents
version, err := semver.NewVersion(strings.TrimSpace(string(data)))
if err != nil {
  return fmt.Errorf("version file %s contains invalid semver %q: %w", path, data, err)
}

Prevention

When it happens

Trigger: A source-state file (e.g. a .chezmoiversion-style file read here) whose trimmed contents fail semver parsing — empty file, plain text, 'v' prefix issues, or multiple lines/whitespace oddities that TrimSpace doesn't fix.

Common situations: Hand-edited version files containing notes or '>=1.2.3' constraints instead of a bare semver string; empty file committed by accident; version written as '1.2' (missing patch) which is not valid strict semver.

Related errors


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

Appendix: source

Thrown at internal/chezmoi/sourcestate.go:2977

			}
		}
	}
	if err := concurrentWalkSourceDir(ctx, s.system, scriptsDirAbsPath, walkFunc); err != nil {
		return nil, err
	}
	return allSourceStateEntries, nil
}

// readVersionFile reads a .chezmoiversion file from sourceAbsPath and returns
// an error if the version is newer that s's version.
func (s *SourceState) readVersionFile(sourceAbsPath AbsPath) error {
	data, err := s.system.ReadFile(sourceAbsPath)
	if err != nil {
		return err
	}
	version, err := semver.NewVersion(strings.TrimSpace(string(data)))
	if err != nil {
		return fmt.Errorf("%s: %q: %w", sourceAbsPath, data, err)
	}
	var zeroVersion semver.Version
	if s.version != zeroVersion && s.version.LessThan(*version) {
		return &TooOldError{
			Have: s.version,
			Need: *version,
		}
	}
	return nil
}

// sourceStateEntry returns a new SourceStateEntry based on actualStateEntry.
func (s *SourceState) sourceStateEntry(
	actualStateEntry ActualStateEntry,
	destAbsPath AbsPath,
	fileInfo fs.FileInfo,
	parentSourceRelPath SourceRelPath,
	targetRelPath RelPath,

View on GitHub (pinned to f901167e46)