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.

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)

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.