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
- Rename the file with a script prefix (run_, run_once_, before_, etc.) so it parses as a script
- Move non-script helper files elsewhere (e.g. .chezmoitemplates or a non-scripts source path)
- 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
- Name scripts with required prefixes (run_, run_once_, run_onchange_, before_, etc.)
- Store non-script helpers in .chezmoitemplates or another allowed location
- Add a pre-commit hook validating every file in .chezmoiscripts parses as a script
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
- %s: cannot add chezmoi's config file to chezmoi, use a confi
- %s: cannot add chezmoi file to chezmoi (%s is protected)
- %s: parent directory not in source state
- %s: not a directory
- %s: %w
AI-assisted analysis of twpayne/chezmoi@f901167e46 (2026-09-01).
Data as JSON: /api/errors/954374de76c15ce6.
Report an issue: GitHub.