Auto-sort cspell.txt on save across multiple worktrees in Cursor
Introduction
If you keep adding words to cspell.txt, the file will gradually lose its ordering. Spell checking still works, so it is easy to leave it alone, but the diffs become harder to review and duplicate words become easier to miss.
That is why it is helpful to sort the dictionary automatically whenever you update it. In this article, I will show how to run sort -u every time cspell.txt is saved in Cursor, and how to make the same rule work across multiple worktrees.
What cspell is
cspell is a spell checker designed for codebases. It looks at words appearing in comments, identifiers, Markdown, and similar text, then reports likely spelling mistakes. The official Getting Started and Configuration pages are good references if you want the overall picture.
Its configuration can live in the cspell field of package.json or in cspell.json style config files. In that configuration, you can either list accepted words directly in words, or load text files such as cspell.txt as custom dictionaries via the Custom Dictionaries mechanism.
In other words, package.json and cspell.json are where the configuration lives, while cspell.txt is the project dictionary referenced by that configuration. In many projects, valid terms such as domain language are not part of a general-purpose dictionary, so maintaining an additional dictionary quickly becomes useful.
The practical setup looks something like this:
{
"name": "your-project",
"cspell": {
"version": "0.2",
"dictionaryDefinitions": [
{
"name": "project-words",
"path": "./cspell.txt",
"addWords": true
}
],
"dictionaries": ["project-words"]
}
}
# cspell.txt
productname
worktree
In this example, the cspell configuration lives in package.json, and cspell.txt is loaded as a custom dictionary. If your project already manages a .txt dictionary file like this, it is usually clearer to keep project-specific words there instead of duplicating the same entries in words.
This article focuses on the case where a project already manages a text dictionary file such as cspell.txt, and explains how to keep that file automatically sorted on save.
What I wanted
The goal itself is simple. I wanted to run the following command automatically whenever cspell.txt is saved.
LC_ALL=C sort -u "${file}" -o "${file}"
Each part has a purpose:
sort: sorts lines-u: removes duplicate lines-o "${file}": writes the result back to the same fileLC_ALL=C: helps keep the ordering stable across environments
For something at this scale, you could always run this command manually, but having it run on save is much more comfortable.
Workspace settings are fine for a single repository
If you only work on one repository, putting the rule in .vscode/settings.json is often enough. The setting stays local to that repository and does not affect anything else.
However, things change when you use multiple worktrees on a daily basis.
User settings are more practical for multiple worktrees
Imagine a structure like this:
/Users/you/dev/your-project/cspell.txt
/Users/you/worktrees/your-project/feature-a/cspell.txt
/Users/you/worktrees/your-project/feature-b/cspell.txt
In that case, it is much easier to define the rule once in Cursor user settings than to repeat the same .vscode/settings.json setup in every worktree. When you create a new worktree, the automation works immediately without any extra setup.
A common VS Code setup may not work in Cursor as-is
If you search for “run command on save in VS Code,” you will often find examples using emeraldwalk.RunOnSave.
In my environment, however, I could not install that extension directly in Cursor. Even if an extension is common on the VS Code Marketplace, it may not always be available through Cursor in the same way.
pucelle.run-on-save worked well in Cursor
I ended up using pucelle.run-on-save. Add the following settings to Cursor user settings at ~/Library/Application Support/Cursor/User/settings.json.
{
"runOnSave.defaultRunIn": "backend",
"runOnSave.commands": [
{
"match": "[\\\\/]your-project(?:[\\\\/][^\\\\/]+)?[\\\\/]cspell\\.txt$",
"command": "LC_ALL=C sort -u \"${file}\" -o \"${file}\"",
"async": false,
"doNotDisturb": true
}
]
}
With this setup, sort -u runs only when saving cspell.txt files under the target project.
What the match pattern means
In this setup, match is what scopes the save hook to the files you actually want:
[\\/]your-project(?:[\\/][^\\/]+)?[\\/]cspell\.txt$
This pattern is intended to match these two cases:
.../your-project/cspell.txt.../your-project/<worktree-name>/cspell.txt
In other words, it covers both the repository root cspell.txt and a cspell.txt placed one directory deeper under a worktree path.
At the same time, it intentionally does not match much deeper directory structures. If your team uses a different layout, you can adjust the regular expression to fit it.
You could use a much broader pattern like cspell\\.txt$, and it would work. But then the rule might also fire for unrelated projects.
Save hooks are convenient, but broad rules can create confusing side effects later.
Setup steps
The full setup process looks like this:
- Install
pucelle.run-on-savein Cursor - Add the configuration to
~/Library/Application Support/Cursor/User/settings.json - Reload the window if needed with
Developer: Reload Window - Add one unsorted word to
cspell.txt - Save the file and confirm it gets reordered automatically
How this differs from pre-commit hooks
This setup mainly improves local developer experience. If you want to guarantee sorted dictionaries across the whole team, you can also enforce the rule in a pre-commit hook or with lint-staged.
A practical split is:
- auto-sort on save: keeps your local workflow comfortable
- pre-commit enforcement: keeps the team-wide state consistent
Conclusion
For files like cspell.txt, automatically sorting them whenever you add new words noticeably reduces maintenance cost. If you work with multiple worktrees, moving the rule into Cursor user settings is especially effective.
It is a small automation, but small automations like this make shared dictionary files much easier to maintain. If you use Cursor with multiple worktrees, this is a worthwhile improvement.