twpayne/chezmoi · error

%s: not a script

Error message

%s: not a script

What it means

This error is produced when chezmoi encounters a source file that looks like a script (e.g. its name starts with 'run_', 'runonce_', or 'before_') at a point where an actual script is required, but the entry does not satisfy the script criteria — most commonly when applying or validating the source state and a file matched by a script pattern is not actually usable as one. The format '%s: not a script' names the offending source-state target path. Common causes include a create_ or modify_ file whose target was passed to script handling, or a script-named entry of an unexpected kind (such as a directory or symlink) inside the source directory. To resolve it, inspect the named source entry in ~/.local/share/chezmoi: either rename it so it is treated as a regular managed file, or make it a proper script file with a supported prefix (run_, runonce_, before_) and executable content.

Source

Thrown at internal/chezmoi/sourcestate.go:2941

		switch {
		case err != nil:
			return err
		case strings.HasPrefix(fileInfo.Name(), Prefix):
			return fmt.Errorf("%s: not allowed in %s directory", sourceAbsPath, scriptsDirName)
		case strings.HasPrefix(fileInfo.Name(), ignorePrefix):
			if fileInfo.IsDir() {
				return fs.SkipDir
			}
			return nil
		case fileInfo.IsDir():
			return nil
		case fileInfo.Mode().IsRegular():
			fa, err := parseFileAttr(sourceName.String(), s.encryption.EncryptedSuffix())
			if err != nil {
				return err
			}
			if fa.Type != SourceFileTypeScript {
				return fmt.Errorf("%s: not a script", sourceAbsPath)
			}
			targetRelPath, err := parentSourceRelPath.Dir().TargetRelPath(s.encryption.EncryptedSuffix())
			if err != nil {
				return err
			}
			targetRelPath = targetRelPath.JoinString(fa.TargetName)
			if s.Ignore(targetRelPath) {
				return nil
			}
			var sourceStateEntry SourceStateEntry
			targetRelPath, sourceStateEntry = s.newSourceStateFile(sourceAbsPath, sourceRelPath, fa, targetRelPath)
			addSourceStateEntry(targetRelPath, sourceStateEntry)
			return nil
		default:
			return &UnsupportedFileTypeError{
				absPath: sourceAbsPath,
				mode:    fileInfo.Mode(),
			}

View on GitHub (pinned to f901167e46)

Solutions

  1. Rename the file with a script prefix (run_, run_once_, before_, etc.) so it parses as a script
  2. Move non-script helper files elsewhere (e.g. .chezmoitemplates or a non-scripts source path)
  3. Exclude auxiliary files from the scripts dir or mark them ignored with the ignore prefix

Example fix

// before
.chezmoiscripts/notes.txt
// after
.chezmoitemplates/notes.txt  (or delete/rename to run_setup.sh)
Defensive patterns

Strategy: validation

Validate before calling

#!/bin/sh
# every regular file in .chezmoiscripts must parse as a script (script naming or encrypted script suffix)
for f in .chezmoiscripts/*; do
  case "$(basename "$f")" in
    run_*|run_once_*|run_onchange_*|before_*|before_once_*|*.encrypted) ;;
    *) echo "not a script: $f"; exit 1;;
  esac
done

Prevention

When it happens

Trigger: Placing a regular (non-encrypted-suffix, non-script-named) file such as a data file, README, or helper config directly inside .chezmoiscripts so parseFileAttr resolves fa.Type != SourceFileTypeScript.

Common situations: Dropping helper files/notes into the scripts directory; scripts missing the required run_/before_/once_ style naming so their type parses as something else; encrypted payloads whose suffix makes them non-script.

Related errors


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