# fastmod drops codemod's Python regex for Rust's, and that changes your replacement strings

> fastmod is a Rust find-and-replace tool for large refactors, built around one workflow: check your regex in interactive mode, then apply it everywhere. It gives that up deliberately, dropping codemod's --start, --end and --count options in exchange for speed, and it replaces Python regex semantics with the Rust regex crate, which has no lookaround or backreferences.

**facebookincubator/fastmod** — A fast partial replacement for the codemod tool. Assists with large-scale codebase refactors via regex-based find and replace with human oversight and occasional intervention.

- Repository: https://github.com/facebookincubator/fastmod
- Stars: 1,934 · Forks: 56
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/facebookincubator-fastmod

## One workflow, chosen over codemod's flexibility

The README states the philosophy before anything else, and it is a subtraction rather than an addition. fastmod is focused on the case of checking a regex in interactive mode and then applying it everywhere, and it is much faster than codemod at that job.

The cost is named explicitly. fastmod does not support codemod's `--start`, `--end` or `--count` options, and it does not support anything like codemod's Python API. So the tool is deliberately not a superset. If your migration plan is a script that walks a line range or counts matches, fastmod is the wrong instrument, and the README says so before you install it.

The interactive loop is what you get instead. For each match, you are shown a coloured diff and asked whether to accept the change, reject it, or edit the line in your `$EDITOR` of choice. That third option is the one that makes it usable on codebases where a regex is right nine times out of ten and wrong on the tenth.

The deprecation example in the README is a `<font>` to `<span>` conversion with the colour attribute moved into a style:

```bash
fastmod -m -d /home/jrosenstein/www --extensions php,html \
    '<font *color="?(.*?)"?>(.*?)</font>' \
    '<span style="color: ${1};">${2}</span>'
```

Note the single quotes on the replacement. That is not stylistic.

## Rust regex syntax will break your Python habits

codemod uses Python regexes and fastmod uses the Rust regex crate, which supports a slightly different syntax and does not support look around or backreferences. Three specific consequences follow, and all three are documented.

Capture groups are referenced as `${1}` rather than `\1`. A literal dollar sign in the replacement is written `$$` rather than escaped. And patterns that rely on lookahead, lookbehind or backreferences simply do not compile.

The last of those is the migration risk. A pattern that worked under codemod because Python's engine has lookaround needs rewriting by hand, and there is no compatibility shim.

The second is a shell issue that catches people even after they have read this. Because bash itself interprets `$` inside double quotes, single-quoting the replacement is important rather than tidy. If you must double-quote the text, the README warns to escape `$` carefully. In the example above, `${1}` and `${2}` are both shell expansions if you forget the quotes.

The regex crate documentation is the reference for the syntax details, and `fastmod --help` is the reference for the options.

## Positional paths, and why piping through xargs backfires

The one usability improvement over codemod is that files and directories can be passed as extra positional arguments after the regex and the substitution. The same deprecation can therefore be written without the `-d` flag:

```bash
fastmod -m --extensions php,html \
    '<font *color="?(.*?)"?>(.*?)</font>' \
    '<span style="color: ${1};">${2}</span>' \
    /home/jrosenstein/www
```

That is what lets you process a file list assembled somewhere else, which is how you would run fastmod against a subset of a repository rather than the whole tree.

The README attaches an important warning to this flexibility. fastmod does its own parallel directory traversal internally, so running `find ... | xargs fastmod ...` may be much slower than letting fastmod traverse the directory itself. If you are reaching for xargs out of habit, you are probably making it worse: the parallelism lives inside the tool, and handing it a stream of individual files serialises the work.

The `--extensions` flag is the other lever for narrowing scope, and combining it with explicit paths is the practical way to stage a large refactor.

The dependency list confirms the shape of the traversal. It pulls in the `ignore` and `grep` crates from ripgrep, which is where the gitignore-aware, multi-threaded walk comes from.

## Building and installing

The install path is cargo, on stable Rust:

```bash
cargo install fastmod
```

Building from source takes three commands:

```bash
git clone https://github.com/facebookincubator/fastmod.git
cd fastmod
cargo build --release
```

and the binary lands at target/release/fastmod, so you can check it with `./target/release/fastmod --help`. If you have built from source and want that build on your path, the README notes that `cargo install` will install your build.

Two release profile settings are worth knowing if you are benchmarking or packaging. The release profile sets codegen-units to 1 and enables LTO, which favours runtime speed over build time, and that is consistent with a tool whose selling point is throughput on large trees.

The manifest declares edition 2024 and Apache-2.0 licensing, with the description calling it a fast, partial replacement for codemod. The version in Cargo.toml is 0.4.5 while the newest tagged release in the repository is v0.4.4 from 2023-12-01, so the working tree is slightly ahead of the last tag. The last push was on 2026-07-28, and there are commits landing after that tag, which is the gap to be aware of if you pin a version.

## How the pipeline actually works, and where it stops

The "How fastmod works" section describes five steps. The `ignore` crate walks the directory hierarchy with multiple threads in parallel while respecting `.gitignore`. The `grep` crate matches each file. Matching files are read into memory. The regex substitution is applied one match at a time. The `diff` crate presents the results as patches for human review.

Two things follow. First, respecting.gitignore is the default behaviour. Second, the interactive review step sits at the end of the pipeline rather than the beginning, which is why the tool can be fast and still safe: nothing is written without a decision per match.

The limitation is platform. fastmod is primarily supported on macOS and Linux, and has been reported to work reasonably well on Windows. The two portability concerns named are the use of `$EDITOR` with a fallback and the console UI, which descends from codemod's ncurses-based text colouring and screen clearing code. The maintainers set the boundary for contributions: fixes using POSIX or Rust standard library equivalents are welcome, but they would rather avoid a direct winapi dependency or substantially growing the dependency graph for Windows-only enhancements.

The console UI is also the reason this is not a tool for a CI job. A tool whose core interaction is a coloured diff requiring an accept, reject or edit decision needs a human at the keyboard, and the dropped scripting options reflect that.

## Conclusion

fastmod fits a refactor where you want to see every change as a diff before it lands, on a repository large enough that codemod's speed is the bottleneck, and where you are willing to drop lookaround from your patterns. It does not fit a scripted, unattended migration, since --start, --end and --count are unsupported and there is no Python API to wrap. Verify that your pattern compiles under the Rust regex crate and that your shell quoting preserves the $ characters in the replacement.

## FAQ

### How do I install fastmod?

The README calls `cargo install fastmod` the easiest route. To build from source, clone the repository, run `cargo build --release`, and the binary appears at ./target/release/fastmod.

### Why does my fastmod replacement produce nothing?

Two documented causes. The Rust regex crate has no lookaround or backreferences, so patterns that relied on Python regex features will not compile, and `$` in a double-quoted replacement is expanded by bash, which is why the README uses single quotes and `${1}` instead of `\1`.

### What codemod options does fastmod not support?

fastmod does not support --start, --end or --count, and it does not support anything like codemod's Python API. It says it supports most other codemod options.

### Should I pipe a file list into fastmod with xargs?

Probably not. The README warns that fastmod does its own parallel directory traversal internally, so `find ... | xargs fastmod ...` may be much slower than letting fastmod traverse the directory. You can pass files and directories as positional arguments after the regex and substitution instead.

### Does fastmod work on Windows?

It has been reported to work reasonably well, but it is primarily supported on macOS and Linux. The named portability concerns are `$EDITOR` with a fallback and the ncurses-derived console UI.

### Does fastmod respect .gitignore?

Yes. It uses the ripgrep `ignore` crate to walk the directory hierarchy in parallel while respecting .gitignore, so ignored files are excluded by default.

## Sources

- [facebookincubator/fastmod on GitHub](https://github.com/facebookincubator/fastmod)
- [Issues](https://github.com/facebookincubator/fastmod/issues)
- [License: Apache-2.0](https://github.com/facebookincubator/fastmod/blob/main/LICENSE)
- [README](https://github.com/facebookincubator/fastmod/blob/main/README.md)
- [Releases](https://github.com/facebookincubator/fastmod/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/facebookincubator-fastmod
