modd: a modd.conf file that maps file patterns to prep commands and daemons
A flexible developer tool that runs processes and responds to filesystem changes
At a glance
- What is it?
- modd is a single Go binary that watches filesystem changes and runs prep commands or restarts daemons based on pattern blocks in a portable modd.conf. It is a good fit for Go and front-end workflows that need to rerun tests or restart a server on change, and a poor fit if you need a plugin ecosystem or a GUI.
- Who is it for?
- Adopt modd if you want a single binary, a checked-in modd.conf, and prep/daemon semantics that map cleanly onto Go tests or a front-end build. Do not adopt it if you need cross-platform file watching without symlink caveats, a plugin ecosystem, or a release cadence newer than v0.8 from January 2019.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 87 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem modd solves: rerunning commands when files change, without a build system
A developer editing Go source wants go test to run when a .go file changes, but only for the module that changed. A front-end developer wants a rebuild and a browser reload when a source file changes. Both cases need the same primitive: watch paths, match them against patterns, then run something. modd provides that primitive and nothing else. The README describes it as a developer tool that triggers commands and manages daemons in response to filesystem changes. There is no build graph, no dependency resolution, and no task DSL beyond the block syntax in modd.conf. That is the point. The tool is aimed at engineers who want to replace the watch half of Gulp or Grunt with a config file they can check into the repository. The README says the modd.conf file is meant to be portable and can safely be checked into source repositories, and that functionality users will want to customize, like desktop notifications, is controlled through command-line flags instead. That split matters: the shared config stays stable, personal preferences stay on the command line.
How a modd.conf block turns file changes into prep runs and daemon restarts
modd looks for modd.conf in the current directory on startup. The file contains one or more blocks, each with a set of file patterns and a set of commands. Commands come in two flavors. Prep commands run and terminate, and are the place for compiling, running test suites, or running linters. Daemon commands run and keep running, and are the place for databases or webservers. The execution order is defined: prep commands run in order of occurrence, and if any prep command exits with an error, execution of the current block stops immediately. If all prep commands succeed, daemons in the block are restarted, also in order of occurrence. If multiple blocks are triggered by the same set of changes, they run in order from top to bottom. Daemons receive SIGHUP by default when their block is triggered, and are restarted if they ever exit. The README gives this two-block example for hacking on devd: a block matching every .go file runs go test @dirmods, and a second block matching .go files except *_test.go runs go install ./cmd/devd and then starts devd -m ./tmp with the +sigterm flag. That flag changes the restart semantics, because devd treats SIGHUP as a browser livereload trigger rather than an exit, so the second block uses SIGTERM to force an actual restart. Two variables carry the changed set into commands: @dirmods expands to a properly escaped list of all directories containing changed files, and @mods contains all changed files. On first run, @dirmods includes all directories containing matching files, which is why the quick-start example runs the tests of all Go modules the first time modd is run.
Batching, negation, and the pattern rules that decide which block fires
modd batches up changes until there is a lull in filesystem activity, so a process that touches many files, like a compilation or a render, is likely to trigger commands only once. Patterns match on a batch of changed files, and a block is triggered when the first match in a batch is seen. Patterns and the paths they match against are always slash-delimited, even on Windows. Paths are cleaned and normalized before matching, with redundant components removed. If a path is within the current working directory the normalized path is relative to it, otherwise absolute. The README flags one consequence directly: a pattern like ./*.js will never match, because inbound paths do not carry a leading ./, so use *.js instead. Negation uses a leading exclamation mark, and for quoted patterns the mark goes outside the quotes. Negations are applied after all positive patterns: modd collects files matching the positive patterns, then removes files matching the negation patterns. Patterns can be naked or quoted, with single or double quotes and backslash escapes inside quoted strings. Ignore handling has its own layer. Common nuisance files such as VCS directories and swap files are ignored by default, and the -i flag lists the current ignore set. A special +noignore flag disables the default ignore patterns for a block, which the README illustrates with a block matching .git/config. There is also an empty match pattern: a block with no pattern runs prep commands once at startup and restarts daemons if they exit, but never signals them to restart on its own.
Installing modd and running it against a real modd.conf
modd ships as a single binary with no external dependencies, released for OSX, Windows, Linux, FreeBSD, NetBSD and OpenBSD. The README directs you to the releases page to download the package for your OS and copy the binary somewhere on your PATH. If you have Go 1.17 or newer, you can install from source with go install github.com/cortesi/modd/cmd/modd@latest. The README notes that CGO is required, so if it is disabled you need to prepend CGO_ENABLED=1. The quick-start config is two lines: a pattern of **/*.go followed by a block containing prep: go test @dirmods. Running modd with no arguments reads modd.conf from the current directory. The README states that modd interprets commands using a built-in POSIX-like shell based on mvdan/sh, and that some external shells are supported by setting the @shell variable in modd.conf. The command-line surface mentioned in the README is small: -i lists the default ignore patterns, and the +noignore and +sigterm flags modify block behavior. The examples directory in the repository contains frontend.conf (React, Browserify, Babel), go.conf (live unit tests for Go), and python.conf (Python with Redis and devd-managed livereload), which is the fastest way to see the syntax applied to a stack close to yours.
Symlinks and the limits of what modd will watch for you
The README is explicit that modd does not implicitly traverse symlinks. To monitor one, you split the path specification from the matching pattern, as in a block headed by mydir/symlinkdir foo.*. Behind the scenes, the README says, modd resolves the symlinked directory as if it had been specified directly by the user, and the supplied text trails off mid-sentence at the case where the symlink destination lies outside the current working directory. That is a gap in the documentation you should treat as unresolved rather than assume an answer for. There is a second boundary worth naming: the pattern language is glob-based, not a full expression language. Negation is applied as a subtraction step after positive matching, so you cannot express conditional logic inside a pattern. And the daemon contract is a signal contract. If your process does not handle SIGHUP, the default behavior of signalling it on every trigger may not be what you want; the devd example exists precisely because SIGHUP meant livereload there, not restart. You can override with +sigterm, but the README only documents those two signals in the material available, so any other signal choice is unverified.
Where modd sits next to a general task runner or a language-native watcher
The obvious comparison is a task runner such as Gulp or Grunt, which the README itself invokes: the frontend.conf example is described as a front-end project where modd and devd replace many functions of Gulp or Grunt. The difference in approach is structural. A task runner builds an in-memory pipeline of streams and plugins, and the watch behavior is one plugin among many. modd has no plugin system. Its unit of work is a shell command string, and its state is the modd.conf file plus the running daemon processes. A language-native watcher, by contrast, lives inside your toolchain and knows your module graph. modd does not know your language. It knows patterns and batches, and hands the changed set to your command through @mods and @dirmods. That is why the Go example can run go test only on the enclosing module: the module awareness comes from @dirmods plus go test's own argument handling, not from modd parsing go.mod. If you want a watcher that understands your build graph, modd is the wrong layer. If you want one config file that drives a compiler, a test suite, and a long-running server with a defined restart order, modd's model is narrower and easier to reason about.
Maintenance, releases, and the MIT licence
The repository is not archived, and the last push recorded is 2026-06-21, but the most recent release listed is v0.8 from 2019-01-20, preceded by v0.7 in July 2018 and v0.6 in April 2018. A reader should read that combination carefully: the codebase sees activity, while tagged releases have not moved in years. For a tool this small, that is not automatically a problem, since the surface area is a config parser, a watcher, and a process manager. It does mean that if you pin to a release, you are pinning to a 2019 artifact, and if you want newer behavior you are building from master. The go install path with @latest also pulls whatever master currently is, which is a different risk profile from downloading a release binary. The project is MIT licensed, which is permissive and places few obligations on how you redistribute or embed the binary; this is a description of the licence identifier, not legal advice, and you should read the LICENSE file in the repository if the distinction matters to your organization. The Travis CI badge in the README points at a service whose status for this repository you cannot infer from the material here, so treat continuous integration claims as unverified.
Editorial conclusion
Adopt modd if you want a single binary, a checked-in modd.conf, and prep/daemon semantics that map cleanly onto Go tests or a front-end build. Do not adopt it if you need cross-platform file watching without symlink caveats, a plugin ecosystem, or a release cadence newer than v0.8 from January 2019. Before committing, verify three things on your own machine: that the default ignore list produced by modd -i does not hide files you care about, that your daemon handles the signal modd actually sends (SIGHUP unless you add +sigterm), and that your symlinked directories behave as you expect, since the README states modd does not implicitly traverse symlinks and resolves a split path specification instead.
Community notes