{"slug": "i-built-format-on-save-for-everything-that-isn-t-an-editor", "title": "I Built Format-on-Save for Everything That Isn't an Editor", "summary": "A developer built onwrite, an open-source tool that runs format-on-save for files written by generators, shell commands, and coding agents, not just editors. It detects the project's existing formatters (Prettier, Ruff, gofmt, etc.) and formats only the files changed by a wrapped command, with a safety model that prevents data loss. The tool is available for macOS, Linux, and Windows under the MIT license.", "body_md": "Editors have had format-on-save for years. The rest of the development environment has not.\n\nWhen 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`\n\n, 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.\n\nI built [onwrite](https://github.com/hunr-ai/onwrite) to run the project's own tools at the point of the write.\n\nThe command I use most is:\n\n``` bash\n$ onwrite run -- ./scripts/codegen.sh\nonwrite: formatted src/client.ts (eslint, prettier)\nonwrite: formatted api/models.py (ruff-check, ruff-format)\n```\n\n`onwrite run`\n\nwaits 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.\n\nThere is also a watcher for writes that cannot be wrapped:\n\n``` bash\n$ onwrite watch\n```\n\nAnd a diagnostic command, because automatic detection is only useful when it can explain itself:\n\n``` bash\n$ onwrite doctor\n```\n\n`onwrite`\n\ndoes not bring its own formatting rules. It looks for the choices already present in the repository: a Prettier config, Ruff settings in `pyproject.toml`\n\n, `.clang-format`\n\n, `Cargo.toml`\n\n, the Go toolchain, and so on. Project-local binaries are resolved before global ones, so the pinned version wins.\n\nThe 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.\n\nThat 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`\n\nleaves the file alone. A formatter that hangs is timed out. One that exits successfully but returns nothing is not allowed to empty the file.\n\nThe 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.\n\nFor 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 --`\n\n.\n\nThe 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.\n\nIt is still `v0.1.0`\n\n. I expect real repositories to uncover combinations I have not seen yet.\n\nInstall it on macOS or Linux:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/hunr-ai/onwrite/main/scripts/install.sh | sh\n```\n\nThen run:\n\n```\nonwrite doctor\nonwrite run -- your-command-here\n```\n\nThe project is MIT licensed: [github.com/hunr-ai/onwrite](https://github.com/hunr-ai/onwrite).\n\nI wrote a more detailed account of the safety model and the decisions behind `run`\n\n, tool detection and agent synchronization on my site: [Format-on-save, after the editor](https://amartya-gaur.com/blog/format-on-save-after-the-editor/).\n\nIf you try it, tell me where its detection was wrong. That is the feedback most likely to improve the next release.", "url": "https://wpnews.pro/news/i-built-format-on-save-for-everything-that-isn-t-an-editor", "canonical_source": "https://dev.to/amartyadev/i-built-format-on-save-for-everything-that-isnt-an-editor-3dpm", "published_at": "2026-08-23 11:15:57+00:00", "updated_at": "2026-08-23 11:43:49.951071+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["onwrite", "hunr-ai", "Prettier", "Ruff", "gofmt", "Claude Code", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/i-built-format-on-save-for-everything-that-isn-t-an-editor", "markdown": "https://wpnews.pro/news/i-built-format-on-save-for-everything-that-isn-t-an-editor.md", "text": "https://wpnews.pro/news/i-built-format-on-save-for-everything-that-isn-t-an-editor.txt", "jsonld": "https://wpnews.pro/news/i-built-format-on-save-for-everything-that-isn-t-an-editor.jsonld"}}