docs.rs failed to build atomwrite-0.1.37
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build:
atomwrite-0.1.36
Read in Portuguese / Leia em Português
atomwrite
- Atomic file operations for LLM agents: one CLI, all-or-nothing writes
- Agent-readable summary: llms.txt and llms-full.txt
- Jump to: Installation, Quick Start, Global Flags, Commands, Configuration, Exit Codes
What Is It
- A single Rust binary that handles every file operation an LLM agent needs
- 47 subcommands (SSOT: clap
Commands/agent-surface), all listed with an example in Commands - Every write is atomic: tempfile, fsync, rename, fsync directory
- Every response is NDJSON: one JSON object per line, machine-readable by default
- Every file gets a BLAKE3 checksum: detect drift, verify integrity, enable optimistic locking
Installation
From crates.io
Slim and full builds
- Features:
core,ast,lang-rust,lang-ts,lang-py,lang-go,lang-full,watch,full(default =core+ast+lang-rust+lang-ts+lang-py;watchis opt-in;semantic-searchis always compiled) - Slim build (core only, 10,908,040 bytes measured on 2026-10-31, DERIVADO 10908040 / 1048576 = 10.40 MiB, local size-gate up to 15 MiB):
cargo install --path . --locked --force --no-default-features --features core - Full/default build (AST languages, 54.4 MiB measured on the v0.1.37 release build of this tree):
cargo install --path . --locked --force - Build with
watch:cargo install --path . --locked --force --features watch(the default binary exits 78 onwatchwith a rebuild hint) - Slim build:
outlineandsparse outlineexit 78 with a rebuild hint (measured), andquery,scope,transformandwrite --syntax-checkhave stubs for the same case (read fromsrc/commands/*_stub.rsandsrc/syntax_check_stub.rs, the slim build was not executed to confirm) cargo xtask check-featureschecks thecoreandcore,watchsets- The features
i18n-cjk,i18n-rtl,i18n-europe,i18n-fullandslow-testsare placeholders or test switches: do not enable them - The 5-8 MB target of the product plan applies to the core profile, not to the default AST build: the measured core build (10.40 MiB) is above that target and under the 15 MiB gate
From source
Shell completions
# Bash
# Zsh
# Fish
Quick Start
# Write a file atomically from stdin
|
# Read it back with checksum
# Search across a directory
# Replace text with atomic writes
# Evaluate math and unit conversions
# Set a value in a TOML file (preserves comments)
# Walk the AST of a Rust file (downloads the parser on first use, see Network And Privacy)
# Extract the outline of a Python file
# Real tree-sitter syntax check before writing (exit 88 on a syntax error)
|
# Report stale WAL sidecars and estimate the reclaim
# Reap terminal journals older than the threshold
Read Expansion In v0.1.37 (2026-10-31)
readtakes one or more paths: explicit paths are read in argv order, and an operand carrying*,?or[is a pattern the binary expands itself against one level of its own directory- Pattern order is the
LC_ALL=Cshell order, taken from the encoded bytes of each name, so one code path sorts on all three targets, and only Linux was executed, while macOS and Windows stay unvalidated --format rawconcatenates every target with no header, no separator and no newline of its own, soatomwrite read --format raw '*.rs' | cmp - <(LC_ALL=C cat *.rs)exits 0 with an empty diff- The batch copies in 64 KiB blocks, so memory stays flat instead of holding the sum of every file, and
--timeout-secsstill cuts the copy with exit 124 because every block advances the cancel check - A target that fails prints one line on stderr and the run continues with the next one, and the final status is the semantic code of the first failure: 4 for a missing path, 13 for a permission, 126 outside the
--workspace read --format rawerrors now go to stderr and never to stdout, because that channel promised bytes and a JSON envelope there was the defect--lines,--line,--context,--head,--tail,--grep,--statand--verify-checksumneed a single target: naming several exits 65 with a message that names the flag**is out of scope: a pattern with**or with more than one separator exits 65 instead of matching something else in silence- The default NDJSON format is unchanged, and one target in
--format rawis byte-identical to 0.1.37 [profile.dev] opt-level = 1cut the dev-profile fuzzy cost from 18533 ms to 4232 ms on the same 15 MB corpus- Pin agents to
^0.1.37. 47 subcommands (SSOT)
What Is New In v0.1.37 (2026-10-31)
- Risk assessment now runs in every verb that writes:
edit,edit-loop,replace,case,transformandcodemodpublishrisk_assessmentwhen the payload matches a content-risk pattern, exactly aswritealready did. The size barriers stay out of these verbs on purpose, because they rewrite a region and a shorter result is the operation working - A grammar this host cannot provide is exit 69
GRAMMAR_UNAVAILABLEwith classprecondition_failed, never 255INTERNAL_ERROR.atomwrite doctorreports the newgrammar_cachecheck with how many of the 23 grammars the AST verbs can need are already cached write.content_risk_patternsis reachable withconfig getandconfig setas a list value, so the risk patterns are XDG configuration instead of a compiled literal (37 keys)log.fileandlog.rotationare accepted in the workspace scope too, and the[log]section of.atomwrite.tomlnow starts the log sink instead of being validated and discardedbatch --transactionpublishes one verdict: therollbackevent carriesok: falsewith thecodeandexitof the failure that caused it, and the transaction locks every target in path order before the snapshot--versionno longer appends-dirty, so an untracked file cannot change the string
What Is New In v0.1.37 (2026-10-15 draft)
- Full history lives in CHANGELOG.md, the upgrade steps live in docs/MIGRATION.md
- New
configverb (the 47th subcommand):config path|list|get|set|unset|doctorreads and writes 37 persistent tuning keys, see Configuration - New
extract-artifactverb: bounded signal from PDF, OOXML and image headers, never raw bytes - Global stream shaping:
--sort-by,--dedupe,--aggregate,--join,--diff-against,--redact,--normalize-encoding,--truncate-content,--max-output-bytes, see Global Flags - The terminal success event carries
ok: true(the per-item events of a stream, such asentryandmatch, do not), and a failing run ends in exactly one terminal event withok: false - Verbs that write take an explicit path (exit 2 without one), and the terminal event carries
target_resolvedandtarget_source: "argv" - Every terminal event also carries
workspace_source:argvwhen the root came from--workspace, andcwdwhen it came from the process working directory edit-loopis all-or-nothing: a pair with no match exits 65 and writes nothing, while--partialwrites the matches and exits 87; the checksum it reads under the lock is the write base, so a concurrent edit exits 82STATE_DRIFT- Atomic writes take an automatic advisory lock (waits up to 5 s, exit 83), and
search,write,editand the other stdin verbs obey SIGTERM, SIGHUP and the deadline (exit 143, 129, 124) watchleft the default feature set, and--versionprintsatomwrite 0.1.37 (<sha>) <target triple>inside a tracked checkout andatomwrite 0.1.37 (no-git) <target triple>outside one- Changes that can break a consumer:
- top-level JSON fields whose value was
nullare no longer emitted: test for an absent key, not fornull --max-output-bytesnow requires--allow-truncate, and each flag alone exits 2semantic-mergewith a conflict exits 84 (MERGE_CONFLICT), andapplywith a unified diff that does not match the file exits 65 and writes nothing- an invalid regex or glob exits 65, not 255, and
get,setanddelon an invalid TOML or JSON file exit 65 case,transform,scopeandcodemodcan exit 82 (STATE_DRIFT) when a file changed during the runsparse listapplies--includeand--exclude, andsearch.max_inflight_bytesstarts at 4 MiB- the Rust library API changed, see Library API
- top-level JSON fields whose value was
- Pin agents to
^0.1.37. 47 subcommands (SSOT). CLI + XDG only (no productATOMWRITE_*knobs; no product telemetry). Error envelopes always emit, with two deliberate exceptions: a broken pipe and a checksum divergence already published as onehash_verifyevent
Why
- LLM agents juggle dozens of shell commands to manipulate files
- A crash mid-write can corrupt a file written in place
- Parsing unstructured CLI output wastes tokens and causes hallucinations
- Agents need checksums to detect concurrent edits but rarely compute them
- atomwrite solves all four problems with one
cargo install
Superpowers
Atomic Writes
- Uses tempfile + fsync + rename + directory fsync on every write
- A reader sees the old file or the new file, never a half-written one
- Durability is set by
write --durability full|fast|auto(defaultauto); a power cut was not simulated in the tests - Optional real syntax check via tree-sitter (
--syntax-checkflag onwrite, exit 88) - Optional WAL sidecar for crash recovery (
.atomwrite.journal.<target>.atomwrite.journal.json)
NDJSON Output
- stdout is ALWAYS structured JSON, one object per line
- Every object carries a
"type"discriminator field - Agents parse output without regex or brittle text scraping
- Errors also emit JSON with
error: trueandok: falseon stdout
BLAKE3 Checksums
- Every
readandwriteresponse includes a BLAKE3 hash - Use
--expect-checksumfor optimistic locking on concurrent edits - Detect state drift before applying changes
Parallel Search
- Built on the ripgrep engine for file content search
- Respects
.gitignoreautomatically - Returns structured matches with file, line, column, and context
AST-Aware Transforms
transformandcodemodrewrite by syntax tree through ast-grep 0.43, which supports 28 languages (measured: each of the 28 names is accepted,ziganderlangexit 65unsupported language)query,outlineand--syntax-checkusetree-sitter-language-pack1.22.1, whose manifest lists 372 languages (measured on 2026-10-31 against the manifest of that version)- Automatic language detection by file extension covers 23 languages (
src/syntax_check.rs);--language NAMEaccepts a parser that is already in the local cache - Refactor code by syntax tree, not fragile regex
Grammatical Scoping
- Select AST categories like comments, functions, classes, and strings
- Apply actions: delete, uppercase, lowercase, titlecase, squeeze, or replace
- Covers Rust, Python, JavaScript, TypeScript, and Go with prepared queries
- Use
--patternfor custom AST patterns beyond the built-in queries
Batch Operations
- Execute write, replace, delete, edit, hash, move, and copy operations from an NDJSON manifest
- Use
--transactionfor all-or-nothing execution with automatic rollback - All operations in a batch share the same atomic guarantees
Structured Config Editing
setwrites a value at a dotted path in TOML or JSON files (preserves comments viatoml_edit)getreads a value at a dotted path with auto-detected formatdelremoves a key (with--force-missingto treat missing keys as no-op success)caserenames identifiers inside files viaheck(snake, camel, pascal, kebab, screaming-snake)
Usage
- All output goes to stdout as NDJSON
- Logs go to stderr: warnings and errors always, info and debug with
-v/-vv - Always pass
--workspace <DIR>to restrict operations to a project root - Use
--dry-runbefore destructive operations - Use
--expect-checksum <HASH>for optimistic locking - Use
--locale <TAG>oratomwrite locale --set/locale --clearfor UI locale (en,pt-BR); NDJSON codes stay English - Configuration is CLI flags + XDG /
.atomwrite.tomlonly, see Configuration - Pipe stdin for
writeandbatchcommands - Agent-native stdout globals (all subcommands):
--select/--fields(CSV top-level keys),--filter KEY=VAL(repeatable AND),--limit N(long-only: there is no global-n, andreplaceowns short-n) - Error NDJSON always has
ok: falseand usuallymatch_count: parseexit/codefirst; success shaping via--filter/--limitnever drops error envelopes
Global Flags
- 33 flags are accepted before or after the subcommand (source:
atomwrite commands --include-globals); a hidden--jsonis also accepted and ignored because the output is always NDJSON - Aliases:
--max-concurrencyfor-j/--threads,--timeoutfor--timeout-secs,--fieldsfor--select - Flags named like a global but local to one command (for example
search --max-filesize, 10 MiB by default) are listed inatomwrite <command> --help
| Flag | Default | What it does |
|---|---|---|
-v, --verbose |
off | raises log verbosity on stderr (-v info, -vv debug, -vvv trace) |
-q, --quiet |
off | lowers it (-q error, -qq off) |
--workspace <DIR> |
current directory | root of the path jail, always pass it |
--config <PATH> |
none | explicit .atomwrite.toml, fails if missing or malformed; replaces both files in config get and config list |
--color <WHEN> |
auto |
auto, always or never |
--no-color |
off | disables colored output |
--ready-file <PATH> |
none | writes the PID after the signal handlers are installed (test harness only) |
--no-gitignore |
off | does not respect .gitignore files |
--hidden |
off | includes hidden files and directories |
--follow-symlinks |
off | follows symbolic links |
-j, --threads <N> |
automatic | worker bound; omitted or 0 = the smaller of logical CPUs, 16 and the RAM cap; an explicit N is honoured, also above 16 |
--max-filesize <BYTES> |
1 GiB | rejects a larger file (read, edit, hash and write exit 65 FILE_TOO_LARGE), and on write it also bounds the stdin payload, so the flag names one ceiling and the payload and the target obey the same one; only search skips silently, with its own local 10 MiB limit counted in files_skipped |
--timeout-secs <N> |
120 | global deadline, 0 disables it, exit 124 with a cancelled event that has deadline_exceeded: true |
--no-progress |
off | disables NDJSON progress events on long operations |
--json-schema |
off | emits the JSON Schema of the command output and exits |
--print-schema [NAME] |
none | lists the 55 schema names, or emits one schema; an unknown name exits 65 |
--non-interactive |
off | declares agent-hosted execution: never prompts, never reads a confirmation from stdin |
--confirm-side-effect |
off | acknowledges a side effect where a command contract asks for it; on a verb that writes nothing it is rejected with exit 65 INVALID_INPUT instead of being a silent no-op |
--allow-truncate |
off | opts in to value truncation, required by --max-output-bytes |
--locale <TAG> |
resolved | en or pt-BR; precedence: this flag, the saved preference, the OS language, en |
--no-auto-heal |
off | skips the startup wal-heal pass (3600 s threshold, 100 ms budget) |
--select <KEYS> |
all keys | keeps only the listed top-level keys (CSV) |
--filter <KEY=VALUE> |
none | keeps events whose key equals the value (!= and == work; repeatable, AND); never drops an error envelope |
--limit <N> |
none | work budget: the target list is cut before the side effect and the walk stops at the record that reaches the cap, so --limit 1 touches exactly one target and a huge tree is not read to the end; and the terminal summary carries walker_stopped_early: true only when the walk was actually cut before the tree ended |
--sort-by <KEY> |
arrival order | sorts events by path-line, line-path or any key (buffers until the end) |
--dedupe <KEYS> |
off | drops events whose key tuple (CSV) was already seen |
--aggregate <KEY[:OP[:FIELD]]> |
off | groups buffered events by KEY; OP is count, sum, min or max |
--join <KEY:SUFFIX> |
off | reads the NDJSON table from stdin once (up to 16 MiB) and attaches the matching rows as a SUFFIX array to every data event that has KEY; events without KEY or without a match, failure events and the summary stay as they were; runs before --select, --filter and --limit (with --sort-by, --dedupe or --aggregate: after the sort, before the dedupe); exits 65 INVALID_INPUT for a TTY stdin, a table above 16 MiB, a line that is not a JSON object, a bad format, a KEY with no row holding a string value, or a command that already reads stdin (write, edit, edit-loop, apply, extract, batch without --file, hash --stdin, calc or regex reading stdin, diff with -) |
--truncate-content <KEY:LEN> |
off | cuts one string key to LEN characters |
--redact |
off | replaces secret-shaped values with [redacted] |
--normalize-encoding |
off | normalizes every string value to Unicode NFC |
--diff-against <PATH> |
off | compares the events against a baseline NDJSON file inside the jail and emits one stream_diff event |
--max-output-bytes <N> |
off | cuts every string value, checksum included; needs --allow-truncate and each flag alone exits 2 |
# Stream shaping examples
|
Commands (47 total — SSOT)
Core I/O
read— read files with metadata, checksum, optional content (--format raworndjson,--head,--tail,--lines)write— create or overwrite files atomically via stdin (large existing targets need--ack-overwrite)edit— surgically edit by line number, text marker, or exact matchedit-loop— apply Nold/newpairs from NDJSON stdin in one atomic writedelete— delete files with optional backup (--planplan-only;--confirm/--yesrejected)copy— copy files with checksum verificationmove— move or rename files atomically (EXDEV copy-fallback)apply— apply patches from stdin (unified diff, search/replace, full, markdown)scratch— create agent staging dir under workspace (never OS/tmp)verify— verify file integrity by comparing BLAKE3 checksum
Search and replace
search— search file contents in parallel (ripgrep engine)replace— replace text across files with atomic writestransform— AST refactoring via ast-grep (28 languages)codemod— multi-rule codemod campaign (YAML rules, delegates to transform)semantic-search— offline token Jaccard search (no network, no embeddings API)semantic-merge— three-way line-based merge (not AST/embedding) for multi-agent writes
Inspection
hash— calculate BLAKE3 checksumsstat— metadata alias forread --stat(size, mode, checksum; no body)count— count lines, files by extension or by sizediff— compare two files (unified, stat, or changes)list— list files in a directory treesparse— budgeted sparse list/read/outline (cap files and bytes for agent context)extract— extract fields from NDJSON input via pipe (positional fields or local--field; never localextract --select: global--selectonly projects output keys)extract-artifact— bounded signal from PDF, OOXML and image headers (never raw artifact bytes)scope— grammatical scoping (delete all comments, etc.)regex— generate regex from examplescalc— math and unit conversionsdoctor— diagnose environment and dependencieslocale— show resolved UI locale / persist preference (XDG)commands— emit full command tree as JSON for agent discoveryconfig— persistent tuning keys:config path|list|get|set|unset|doctor, 37 keys,--scope global|workspacerequired onsetandunset(and onpath)agent-surface— agent tool surface manifest (integration by subprocess, CLI with NDJSON output)watch— watch filesystem events (needs a build with--features watch; the default binary exits 78)completions— generate shell completions (bash, zsh, fish, elvish, powershell)
Backup and recovery
backup— create timestamped backups with BLAKE3 checksumsrollback— restore from a previous backupprune-backups— prune.bak.YYYYMMDD_HHMMSSbackups by age or countbatch— NDJSON-driven batch operations (transactional)recipe— named multi-step recipes (list/run)
Structured config editors
set <PATH> <KEY_PATH> <VALUE>— write a value at a dotted path in a TOML or JSON file, preserving comments and key order viatoml_editget <PATH> <KEY_PATH>— read a value at a dotted path, NDJSON{"type":"get","key_path","value","found","format"}del <PATH> <KEY_PATH>— remove a key,--force-missingtreats a missing key as a no-op successcase <FILES...> --subvert OLD NEW --to <style>— rename identifiers inside the given FILES viaheck(styles:snake,camel,pascal,kebab,screaming-snake); a directory argument expands through the shared walker and honours--includeand--exclude, and a directory that yields no file exits 65 instead of succeeding with nothing renamed
AST tools (via tree-sitter-language-pack)
query <PATH> [--kinds|--query <KIND>|-Q <KIND>|--tree] [--positions]— walk a tree-sitter AST and emit nodes as NDJSONoutline <PATH> [--kind <KIND>] [--positions]— extract high-level structure (functions, classes, structs, enums, traits, modules) as NDJSON; needs a file, not a directorywrite --syntax-check— real syntax check via tree-sitter, exit 88 with first error line, column, kind and message
WAL cleanup
wal-stats— telemetry snapshot: total journals, by-state breakdown, oldest age, total size, by-directory breakdown, auto-heal recommendation, estimated reclaim byteswal-heal <PATH> [--threshold-secs N] [--max-duration-ms N]— reap stale terminal (committed or aborted) journals older than the threshold, and also remove aStartedjournal whose owning PID no longer exists, reported asdead_owner_removed
Common patterns for each subcommand
# read
# write
|
# edit
# edit-loop (N old/new pairs, one atomic write)
|
# scratch: stage under .atomwrite/scratch/, never OS /tmp outside --workspace, for --old-file/--new-file
# verify
# search
# replace
# hash
# delete
# count
# diff
# move
# copy
# list
# extract: positional fields or local --field; NEVER local extract --select
|
|
# thin agent shape (global --select projects output keys only):
# extract-artifact (PDF, OOXML or image header; an image here)
# calc
# regex
# transform
# scope
# batch
|
# backup
# rollback
# prune-backups
# apply
|
# set
# get
# del
# case: a file or a directory (the directory is expanded through the walker)
# query
# outline
# wal-stats
# wal-heal
# stat
# sparse
# semantic-merge (line-based, not AST; exit 84 when there are conflicts)
# recipe
# agent-surface
# watch (needs a build with --features watch; the default binary exits 78)
# codemod
# semantic-search
# doctor
# locale
# commands (full clap tree for agent discovery)
# completions
# config (persistent tuning keys, see Configuration)
codemod --rulesreads a YAML list of items withlanguage,pattern,rewriteand an optionalid; the ast-grep stylerule:andfix:form exits 65prune-backups --dry-runtakes no value: omit the flag to apply the pruning
Configuration
- Precedence: CLI flags, then the workspace
.atomwrite.toml, then the global XDGconfig.toml(the two files are merged key by key), then the built-in defaults config path|list|get|set|unset|doctoris the only way to change a persistent tuning keyconfig set KEY VALUE --scope global|workspaceandconfig unset KEY --scope global|workspacerequire--scope(exit 2 without it, nothing is touched), andconfig pathtakes it tooconfig listandconfig get KEYtake no--scope: they showvalue,origin(default,global,workspaceorexplicitwith--config PATH) anddefault- Scope
globalis the XDGconfig.toml(mode 0600), scopeworkspaceis.atomwrite.tomlin the workspace root setkeeps comments, validates the whole file and writes by tempfile-fsync-rename without a backup; the terminal event carriestarget_resolvedandtarget_source: "argv"- An unknown key or a value out of range exits 65 and writes nothing; a file that is not valid TOML is never rewritten (
setandunsetexit 78);configworks even with a brokenconfig.toml - A flag still wins over the configuration (for example
extract-artifact --max-text-chars)
| Key | Type | Default | Accepted | Scopes |
|---|---|---|---|---|
fuzzy.mode |
string | auto |
auto, aggressive, off |
global, workspace |
fuzzy.threshold |
float | 0.7 | 0.0 to 1.0 | global, workspace |
fuzzy.threshold_aggressive |
float | 0.5 | 0.0 to 1.0 | global, workspace |
fuzzy.threshold_context |
float | 0.8 | 0.0 to 1.0 | global, workspace |
fuzzy.threshold_jw |
float | 0.85 | 0.0 to 1.0 | global, workspace |
fuzzy.soft_floor_delta |
float | 0.05 | 0.0 to 1.0 | global, workspace |
fuzzy.max_content_bytes |
integer | 268435456 (256 MiB) | 1 or more | global, workspace |
write.confirm_large_bytes |
integer | 102400 (100 KiB) | 1 or more | global, workspace |
log.file |
boolean | false |
true, false |
global, workspace |
log.rotation |
string | daily |
daily, hourly |
global, workspace |
storage.home |
string | none | a directory | global only |
locale |
string | none (resolved) | en, pt-BR |
global only |
concurrency.max_threads |
integer | 16 | 1 to 256 | global, workspace |
concurrency.ram_fraction_percent |
integer | 50 | 1 to 100 | global, workspace |
search.max_inflight_bytes |
integer | 8388608 (8 MiB) | 4194304 (4 MiB) to 1073741824 (1 GiB) | global, workspace |
summary.errors_max_entries |
integer | 50 | 1 to 1000 | global, workspace |
lock.acquire_timeout_ms |
integer | 5000 | 100 to 3600000 | global, workspace |
retry.base_ms |
integer | 50 | 1 to 60000, not above retry.max_ms |
global, workspace |
retry.max_ms |
integer | 400 | 1 to 60000 | global, workspace |
retry.max_elapsed_ms |
integer | 2000 | 1 to 3600000 | global, workspace |
extract.max_sample_rows |
integer | 10 | 0 to 100000 | global, workspace |
extract.max_text_chars |
integer | 400 | 1 to 10000000 | global, workspace |
extract.max_sheets |
integer | 32 | 1 to 10000 | global, workspace |
extract.max_slides |
integer | 64 | 1 to 10000 | global, workspace |
- A scope outside the last column exits 65 (for example
config set storage.home /srv --scope workspace) - These fields are
configkeys like every row above, and each answersconfig get:defaults.backup,defaults.retention,defaults.line_ending,defaults.max_filesize,write.shrink_block_percent,write.auto_rotate_max_age_secs,write.content_risk_patterns,watch.idle_exit_ms,watch.debounce_ms,search.context,search.smart_case,search.max_filesizeandsearch.max_columns; an unknown section in.atomwrite.tomlmakesreadexit 78 (measured) - Files on Linux: global config
$XDG_CONFIG_HOME/atomwrite/config.toml, saved locale$XDG_CONFIG_HOME/atomwrite/locale, log$XDG_STATE_HOME/atomwrite/logs/atomwrite.<date>.jsonl(only withlog.file = true, file mode 0600, directory 0700), parsers$XDG_CACHE_HOME/tree-sitter-language-pack/v1.22.1/ - macOS and Windows resolve the same files through the
directoriescrate; those paths were read from the code and not run there storage.homemovesconfigandstateunder{home}/configand{home}/state
Environment Variables
- Product policy: atomwrite does not use product environment variables for runtime knobs (workspace, backup, locale, log filter, home override, WAL, etc.)
- Configure via CLI flags and XDG storage /
.atomwrite.toml(and optional--config) doctorreads no environment variable: host detection (WSL, container, Kubernetes, Termux, Flatpak, Snap) uses file probes only, and it is not a configuration API- Dependencies read some variables on their own, never as atomwrite configuration: the OS-language crate (
LANG,LC_ALL,LC_MESSAGES,LANGUAGE), the XDG crates (HOME,XDG_*) and the parser download client (ALL_PROXY,HTTPS_PROXY,HTTP_PROXY,NO_PROXY); see ADR-0057 in docs/decisions/ - Shell-level variables used by your agent harness (PATH, etc.) are outside atomwrite product config
Network And Privacy
- No telemetry: atomwrite sends nothing about you or your files
- The only network traffic is the parser download used by
query,outlineandwrite --syntax-check(the last one read from the source, not run);transform,scopeandcodemoduse ast-grep parsers compiled into the binary - On the first use of a language, the binary downloads the parser bundle for the platform from
https://github.com/xberg-io/tree-sitter-language-pack/releases/download/v1.22.1/(for Linux x86_64 the fileparsers-linux-x86_64.tar.zst, 23031807 bytes in the manifest, about 22 MiB, plus 3.9 MiB of extracted parser libraries for the six languages this machine used so far); the manifest carries asha256for each platform bundle - The cache lives at
$XDG_CACHE_HOME/tree-sitter-language-pack/v1.22.1/(bundles,libs,manifest.json), and the download happens only while the needed parser is missing there - The download client honours the proxy variables listed in Environment Variables
- Offline use: fill the cache directory on a host with network access and copy it to the offline host
- A panic writes a crash report file
report-<uuid>.tomlin the OS temporary directory (through thehuman-paniccrate) and prints its path; nothing is transmitted, and you choose whether to send it - Details: PRIVACY.md
Exit Codes
- Error envelopes on stdout are always NDJSON with
ok: false(and usuallymatch_count); readexit/codebefore treating a line as success - The list below is derived from the error enum and checked against
agent-surfaceand--help
0SUCCESS: success1NO_MATCHES: no matches found (search, replace, transform, scope; not a crash)1DOCTOR_CHECKS_FAILED:doctor --strictfound failed or warned checks (the report event carries every check)2ARGUMENT_PARSE_ERROR: command line rejected by the parser2UNSUPPORTED_COMBINATION: two flags this verb refuses to combine (readflagandotherin the envelope)4FILE_NOT_FOUND: file not found4GLOB_NO_MATCHES: pattern expanded to no file13PERMISSION_DENIED: permission denied28DISK_FULL: disk full (no space left on device)30QUOTA_EXCEEDED: quota exceeded65INVALID_INPUT: invalid input (bad arguments or malformed data, including empty stdin without--allow-empty-stdinand a file above[fuzzy].max_content_bytesunder fuzzy)65MATCH_FAILED: pattern did not match, or anedit-looppair has no match (readbest_candidate)65MATCH_AMBIGUOUS: pattern matched more than once (readbest_candidate)65BINARY_FILE: binary content rejected65FILE_TOO_LARGE: file above the size limit69GRAMMAR_UNAVAILABLE: the tree-sitter grammar for a language is not on this host (classprecondition_failed, retryablefalse)73CROSS_DEVICE: cross-device rename (filesystem boundary)74IO_ERROR: I/O error78CONFIG_INVALID: configuration invalid, or a verb that needs a feature missing from this build (slim)80TRANSACTION_ROLLBACK_FAILED: batch transaction rollback failed (backups kept)81CHECKSUM_VERIFY_FAILED: checksum verify failed82STATE_DRIFT: state drift (checksum mismatch, optimistic lock failed)83LOCK_TIMEOUT: lock not acquired withinlock.acquire_timeout_ms(default 5000)84MERGE_CONFLICT:semantic-mergefound conflicts85FIFO_DETECTED: FIFO detected (named pipe cannot be atomically written)86DEVICE_FILE: device file detected (block or character device)87EDIT_LOOP_PARTIAL:edit-loop --partialwrote the matching pairs and some pairs missed88SYNTAX_ERROR_DETECTED: syntax error detected (--syntax-checkfailed)91EXDEV_FALLBACK_DISABLED: EXDEV fallback disabled (cross-device copy-fallback is not allowed)92COPY_BACK_BLAKE3_FAILED: copy-back BLAKE3 verification failed93ORPHAN_JOURNAL: orphan journal detected124TIMEOUT: global--timeout-secsdeadline exceeded (default 120; pass0to disable)126WORKSPACE_JAIL: workspace jail violated (path escapes workspace)127SYMLINK_BLOCKED: symlink blocked (target outside workspace)128IMMUTABLE_FILE: file immutable (cannot modify)129SIGHUP: terminal hangup (SIGHUP)130CANCELLED: run cancelled before it finished (status follows the cause)130SIGINT: interrupted by SIGINT141BROKEN_PIPE: broken pipe (SIGPIPE or aBrokenPipewrite error)143SIGTERM: terminated by SIGTERM255INTERNAL_ERROR: internal error
Signal Handling
- Unix: SIGINT (Ctrl+C), SIGTERM and SIGHUP are intercepted for a cooperative shutdown with exit 130, 143 and 129; the run ends with a
cancelledevent that carriessignalanddeadline_exceeded - Unix: SIGPIPE has a handler, it is neither ignored nor left at its default action; the handler marks a cooperative shutdown with exit 141
- The default SIGPIPE action killed the process before any destructor ran and left lock sidecars, journals and tempfiles of the writes in flight; with the handler every
Dropruns and nothing is left behind - A write that fails because the reader closed exits 141 (
BROKEN_PIPE) with no panic and no crash report:completions,--print-schema,--helpand every verb (fixed in 0.1.37, testcli_v0137_broken_pipe_outputs) - Very short output that fits in the pipe buffer (for example
--version | head -1) can finish before the reader closes and then exits 0 - A write that hits the closed pipe fails with
BrokenPipeand exits 141 as well, and a parent that ignores SIGPIPE gets the same exit 141 - A repeated SIGPIPE never forces an exit; a repeated SIGINT, SIGTERM or SIGHUP does
- Windows: Ctrl+C intercepted via console handler; there is no SIGPIPE, and
BrokenPipefromstdfollows the same path (not validated there) - First signal: sets the shutdown flag and prints a message on stderr in the active locale (
atomwrite: shutting down...with--locale en) - Second signal: immediate process termination via
_exit(Unix) orexit(Windows) - Walker threads (search, replace, transform, scope) stop between files
- Batch operations stop between operations
- Stdin verbs (
write,editand the others) obey the signals and the deadline while stdin is still open: SIGTERM exits 143 (measured)
Error Handling
- All errors emit a JSON object on stdout with
error: trueandok: false - Error fields:
code,exit,message,path,error_class,retryable,suggestion,workspace - Every summary that publishes
files_failedaddspartial: trueanderrors[]beside it (each entry haspath,codeandmessage,pathis<walk>when the walk failed without naming a file, at most 50), and that coverssearch,transform,replace,scope,count,list,sparse,semantic-searchandcase - Error classes:
permanent,transient,conflict,precondition_failed - 35 error variants in
src/error/variants.inc.rs - Transient and conflict errors set
retryable: true - The
suggestionfield provides actionable recovery guidance for agents, in the active locale (suggestion_lang) - See
docs/schemas/error-output.schema.jsonfor the full contract
Performance
- Single binary; on Linux it links dynamically against the system C library and nothing else (
lddon the v0.1.37 build listslibgcc_s.so.1,libm.so.6andlibc.so.6) - Release builds use LTO, single codegen unit, and symbol stripping
- Memory-mapped file reads via
memmap2for large files - Parallel multi-file work is the default modus operandi:
ignore::WalkParallel/collect_files_parallel(search content +--target files, replace, count, transform, scope, recursive discovery) +rayon(hash, backup, multi-path + recursive delete/copy, case multi-path, semantic-search scoring, non-txn batch, prune multi-target, sparse read, diff dual-read) - Worker bound:
--threads/-j/--max-concurrency(default = the smaller of the logical CPUs, 16 threads and the RAM cap; an explicit-j Nis honoured as asked; seesrc/concurrency.rs) - Small-file latency: medians of 5.6 ms (
read) and 6.7 ms (write) in one session of 20 runs, and 9.4 ms and 9.7 ms in another of 30 runs, whole process included; machine: Intel Xeon E5-2699 v3 2.30 GHz with 72 logical CPUs, Linux, working directory on tmpfs, measured on 2026-10-19 while other jobs ran on the same host
Known Limits
replace --expect-checksumcompares the checksum under the lock, so a writer that lands between the check and the write is detected as exit 82STATE_DRIFT; verbs that read, change and write back (edit,edit-loop,replace,set,del,config set,apply,write --append) serialize under the lock- A target name long enough to push the sidecar past 255 bytes gets a short BLAKE3 digest as the sidecar name, so a 240-byte target locks instead of failing with
ENAMETOOLONG; a 244-byte target was proven withreplace - The directory of the target must allow creating that lock sidecar: writing to an existing file inside a read-only directory exits 13 even when the file itself is writable
- The RAM ceiling for the automatic worker count reads
MemAvailablefrom/proc/meminfoon Linux only and ignores container (cgroup) limits - Without
--sort-by, the order ofsearchresults varies from run to run because the walkers run in parallel caseexpands a directory argument through the shared walker and honours--includeand--exclude; a directory that yields no file exits 65 instead of succeeding with nothing renamedquery,outlineandwrite --syntax-checkdetect only 23 languages by extension;--language NAMEworks for a parser already extracted in the local cache, and exits 65unsupported language overridefor a language not yet usedextract-artifactemits a singleartifactevent and no summary, so a successful run has no event withok: truelocale --setandlocale --clearprint the resolved language from before the change; a followingatomwrite localeshows the new state- macOS and Windows were not run; the Unix-only code has
cfg(windows)pairs that were only compiled withcargo checkfrom Linux
Library API
- The supported interface of atomwrite is the CLI and its NDJSON contract; the crate also ships a Rust library target that is not covered by that promise
- The public Rust API of v0.1.37 changed against the published 0.1.36 in ways SemVer treats as breaking:
cargo semver-checks0.50.0 reported 10 failed major checks (removedlocale::Idiomaandlocale::{negotiate_to_idioma,resolved_idioma}, removedLocaleSource::EnvVar, removedsignal::reset_sigpipe, four removed lock constants,LockGuardno longerSendandSync, newCommandsvariants and new public struct fields without#[non_exhaustive], andwatchno longer a default feature) - Pin
atomwriteas a library dependency to an exact version (=0.1.37) and read CHANGELOG.md before every upgrade
Troubleshooting FAQ
atomwrite write hangs with no output
- Ensure you are piping content to stdin
writereads from stdin and waits for EOF- Example:
echo "content" | atomwrite --workspace . write file.txt
search returns exit code 1
- Exit code 1 means zero matches were found
- This is expected behavior, not an error
- Check the pattern and target path
search with a pattern that starts with a hyphen
- A pattern that starts with
-goes after--, the wayrgandgrepdo it atomwrite --workspace . search -F -- '-deprecated' src/searches for that literal text- Without
--the token is a flag the verb does not have, so the run exits 2 and prints the parser tipto pass '-Z' as a value, use '-- -Z'
search, replace and transform exit 4 although they printed matches
- A root named in the invocation that does not exist fails the run even when the other roots matched, in
search,replaceandtransform - The matches still reach stdout, and the summary carries
ok: false,code: FILE_NOT_FOUND,files_failedand the path inerrors[] - A file that fails during the walk is the other case:
partial: truejoins the summary, and the run keeps exit 0 insearch,replace,transform,scope,case,sparse,semantic-searchandbackup, whilecount,list,hash,copy,deleteandprune-backupsexit with the first failure because their answer is a total over the tree; the contract is ADR-0077 (docs/decisions/0077-answer-family-and-walk-honesty.md)
cross-device rename fails with exit 73
- The source and destination are on different filesystems
- atomwrite falls back to copy+delete for
moveacross devices - Use
copyfollowed bydeleteas an alternative
checksum mismatch with exit 82
- Another process modified the file between read and write
- Re-read the file to get the current checksum
- Retry the operation with the updated
--expect-checksum
workspace jail violated with exit 126
- The target path resolves outside the
--workspaceboundary - Verify the path does not contain
..traversals or symlinks escaping the workspace
syntax check failed with exit 88
- The real tree-sitter syntax check found a syntax error in the file
- Inspect the first error line/column/kind/message in the JSON error envelope
- Fix the syntax and retry, or remove
--syntax-checkto bypass
empty stdin rejected with exit 65
- The guard rejected 0 bytes from stdin as a likely upstream-pipeline failure
- Pass
--allow-empty-stdinto confirm the empty input is intentional - Or pipe real content:
echo "x" | atomwrite --workspace . write file
edit hangs (or fails with exit 65) on --after-line/--after-match/--between/--multi
- These modes read replacement content from stdin, same as
write - A terminal stdin fails fast with exit 65
INVALID_INPUTinstead of blocking forever - Pipe content:
echo "new text" | atomwrite --workspace . edit file.rs --after-line 10 - Or use
--new-file <PATH>where the mode accepts it --after-match/--before-match/--between/--after-line/--before-line/--range/--delete-rangereject--old/--new/--old-file/--new-fileat parse time (exit 2): these modes only take content from stdin
watch exits 78
- The default binary is built without the
watchfeature - Rebuild with
cargo install --path . --locked --force --features watch
MSRV And Targets
- Minimum supported Rust version: 1.90 (edition 2024);
rust-toolchain.tomlpins channel 1.94 for development - Locales:
enandpt-BR - Linux x86_64 is exercised end to end
- Windows is checked with
cargo check --target x86_64-pc-windows-gnufrom a Linux host only - macOS was not exercised on a real Mac
Documentation
- docs/INSTALL.md: install, features and build profiles
- docs/HOW_TO_USE.md: full usage guide
- docs/COOKBOOK.md: task recipes
- docs/AGENTS.md: contract for LLM agents
- docs/CROSS_PLATFORM.md: platform notes
- docs/MIGRATION.md: upgrade paths between versions
- docs/TESTING.md: how the test suite is organized
- INTEGRATIONS.md: using atomwrite from agents and scripts
- PRIVACY.md: what the binary reads, writes and downloads
- llms.txt and llms-full.txt: summaries for LLM context windows
Architecture
- See ARCHITECTURE.md for module map, data flow, and design decisions
- See docs/decisions/ for the index of 60 ADRs (0019 to 0078; 3 more live in
docs/adr/) - Verification is running the program, not a test suite: every claim in this README is backed by a real invocation of the binary, and
docs/TESTING.mddescribes the harness for those cases a maintainer chooses to keep - See docs/schemas/ for the JSON Schema contracts of the NDJSON output, where 54 files describe the 55 names
atomwrite --print-schemalists
Contributing
- See CONTRIBUTING.md for development setup, local DoD, PR process, and release notes
- See CONTRIBUTING.pt-BR.md for the Portuguese guide
- Follow the Code of Conduct / Código de Conduta
Security
- See SECURITY.md for vulnerability reporting
- See SECURITY.md section "Known Security Advisories" for resolved and active advisories
- See PRIVACY.md for data handling
Changelog
- See CHANGELOG.md for English release history
- See CHANGELOG.pt-BR.md for Portuguese release history
License
- Licensed under MIT OR Apache-2.0
- See LICENSE-MIT and LICENSE-APACHE for details