Editors have had format-on-save for years. The rest of the development environment has not.
When a generator, shell command or coding agent writes a file, the bytes usually land exactly as produced. The repository may already have Prettier, Ruff or gofmt, but nothing asks it to run. The mistake is found later by a pre-commit hook, CI, or the agent itself after it has moved on.
I built onwrite to run the project's own tools at the point of the write.
The command I use most is:
$ onwrite run -- ./scripts/codegen.sh
onwrite: formatted src/client.ts (eslint, prettier)
onwrite: formatted api/models.py (ruff-check, ruff-format)
onwrite run waits for the wrapped command to finish, works out which files it changed, and formats only those files. The command's input, output and exit code pass through unchanged.
There is also a watcher for writes that cannot be wrapped:
$ onwrite watch
And a diagnostic command, because automatic detection is only useful when it can explain itself:
$ onwrite doctor
onwrite does not bring its own formatting rules. It looks for the choices already present in the repository: a Prettier config, Ruff settings in pyproject.toml, .clang-format, Cargo.toml, the Go toolchain, and so on. Project-local binaries are resolved before global ones, so the pinned version wins.
The implementation is deliberately cautious. A formatter never receives the real source file. The contents go in through stdin, the result comes back through stdout, and the file is replaced atomically only after the formatter chain has succeeded.
That choice handles the failure case I cared about most. If a watcher catches a heredoc halfway through, the partial file will normally fail to parse. The formatter exits with an error and onwrite leaves the file alone. A formatter that hangs is timed out. One that exits successfully but returns nothing is not allowed to empty the file.
The watcher also avoids the usual “formatter wrote the file, so run the formatter again” loop without using a blind delay. It remembers the exact content it wrote. Its own write is ignored, while a real edit made immediately afterwards still has different content and is processed.
For Claude Code, a hook reports when formatting changed a file the agent just wrote. That gives the agent a chance to re-read it instead of continuing with a stale copy. The core tool is not tied to Claude; anything that can run in a shell can be placed after onwrite run --.
The first release has 87 tests, including partial writes, hanging tools, ignored files and write-loop cases. CI runs the race detector and integration tests against real formatters on Linux and macOS. Release binaries are available for macOS, Linux and Windows.
It is still v0.1.0. I expect real repositories to uncover combinations I have not seen yet.
Install it on macOS or Linux:
curl -fsSL https://raw.githubusercontent.com/hunr-ai/onwrite/main/scripts/install.sh | sh
Then run:
onwrite doctor
onwrite run -- your-command-here
The project is MIT licensed: github.com/hunr-ai/onwrite.
I wrote a more detailed account of the safety model and the decisions behind run, tool detection and agent synchronization on my site: Format-on-save, after the editor.
If you try it, tell me where its detection was wrong. That is the feedback most likely to improve the next release.
Top comments (0)