# CompileDaemon has three watchers and one of them needs a raised kernel limit

> CompileDaemon watches your Go source and runs go build when a file changes, nothing more. Its whole design is a small flag table, and its file watching depends on either fsnotify notifications, a polling fallback, or a third watcher library, with inotify exhaustion being the failure mode users hit.

**githubnemo/CompileDaemon** — Very simple compile daemon for Go

- Repository: https://github.com/githubnemo/CompileDaemon
- Stars: 1,723 · Forks: 156
- Language: Go
- License: BSD-2-Clause
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/githubnemo-compiledaemon

## The stated scope is two sentences

The README opens by defining the tool narrowly: it watches your `.go` files in a directory and invokes `go build` if a file changed. Then: nothing more.

That framing matters because the feature list looks long for such a small job. The tool has no caching layer, no dependency graph of its own, no test runner and no output streaming beyond prefixing child process output. What it has is a watch loop, a debounce, and a command to run.

The usage is one flag:

```
$ ./CompileDaemon -directory=yourproject/
```

and installation is one line with the Go tool:

```
go install github.com/githubnemo/CompileDaemon@latest
```

The repository is correspondingly tiny. The top-level entries are `LICENSE`, `README.md`, `daemon.go`, `go.mod`, `go.sum`, `process_posix.go`, `process_windows.go` and `watcher.go`. Four Go source files and a README is the entire project, with the platform split handled by a posix and a windows file for process control.

That also means the feature set is essentially frozen unless someone takes it on. See the maintenance note below.

## The flags are grouped by what they affect, and the defaults matter

The command line options table is organised into four groups, and the defaults are where the real design decisions sit.

Under actions, `-build` defaults to `go build` and lets you substitute the command entirely. `-command` defaults to nothing, runs after a successful build, and is issued with the working directory set to `-directory`. That second one is the restart loop: `-command="./MyProgram -my-options"` keeps a copy of your program running, killing the old one and starting a new one on each successful build.

Under file selection, `-directory` defaults to `.` and is also used as the build directory unless `-build-dir` is given. `-recursive` defaults to true. `-exclude-dir` takes a glob such as `.git` and may be repeated. `-exclude` takes a glob for files, with `.#*` given as the example for ignoring emacs temporary files. `-include` matches on the last path component, and `-pattern` defaults to the regular expression `(.+\.go|.+\.c)$`, so it watches Go and C files.

`-pattern` and `-include` are alternatives rather than complements: you either change the regular expression or you add include globs. The README's example of the latter is `-include=Makefile -include="*.less" -include="*.tmpl"`, which is the way to rebuild on a template change rather than a source change.

Under file watch, `-polling` defaults to false, `-polling-interval` defaults to 100 milliseconds, and `-work-delay` defaults to 900. That last one is the debounce: milliseconds to wait after file changes settle before starting a build, with each new change resetting the timer, and it must be a positive integer.

Under misc, `-color` and `-log-prefix` both default to false and true respectively, and `-graceful-kill` defaults to false, sending SIGTERM on supported platforms so the child can exit cleanly instead of being killed.

## Three watching mechanisms, and only one of them scales

The dependency list in `go.mod` explains how file watching is done, and it names two libraries plus the standard path.

```
require (
	github.com/fatih/color v1.18.0
	github.com/fsnotify/fsnotify v1.8.0
	github.com/radovskyb/watcher v1.0.7
)
```

`fsnotify` is the conventional choice and is the default path, since the default is to use filesystem notifications rather than polling. `radovskyb/watcher` is present as an alternative watcher implementation. `fatih/color` is the terminal colouring behind the `-color` flag.

The `-polling` flag exists because notifications do not always work. The README's Docker section explains the specific case: changes on Docker volumes under macOS are not reported, with the cause described as either the bind filesystem mount between macOS hosts and Docker containers, or the `fsnotify` implementation, or a combination of both. The workaround given is:

```
CompileDaemon -polling
```

and the cost is stated plainly. Polling actively watches for changes and naturally uses more CPU resources, the interval is tunable with `-polling-interval=N`, and you are advised never to use it in production because it is simply too wasteful.

So there is a real trade here. Notifications are efficient and break in containerised macOS environments; polling works everywhere and costs CPU proportional to your tree size. Neither mode is wrong, but they are not interchangeable and the failure looks different.

The module requires Go 1.16, and for development the README says to set `GO111MODULE=on` so you can work outside `$GOPATH/src`, which dates the guidance even though the setting is the default in modern Go.

## Two documented failure modes you will probably hit

The Common Issues section is short and both entries are about resource limits rather than about the build logic.

Too many open files. The README says to exclude your `.git`, `.hg` or similar VCS directories using `-exclude-dir`, and notes this is common on OS X and BSD platforms where each watched file consumes a file descriptor. If excluding is not enough, raise the process limit with `ulimit`, and `ulimit -n 1024` is suggested as probably sufficient. There is also a sysctl-based limit that you may reach and need to adjust.

`filepath.Walk() no space left on device`. This one is mislabelled, which is worth understanding rather than just copying. The README explains that as described in issue 23, what actually runs out is inotify watchers, which are limited by system configuration, and links to documentation on increasing the amount of inotify watchers.

The error message says no space left on device, which sends people looking at disk. It is not disk. It is the kernel's per-user inotify instance limit, and the fix is a sysctl setting rather than freeing space. The README's recommendation is `-exclude-dir` first and system limits second, in that order, because excluding a `.git` directory removes thousands of watches for free.

The Docker section adds a third practical issue, and it is the one that most often pushes people to `-polling` without realising the cost.

## The security section is the most honest paragraph

There is a Security Considerations section, and it reads like this: if you are using CompileDaemon in production to rebuild a binary, an attacker with write access is able to insert arbitrary code into your binary, so make sure you secure write access to the file system appropriately.

The parenthetical in the source is blunter than the deployed text would suggest, asking to be told why anyone would do this, and carrying on regardless. That is a fair characterisation of the tool's scope. A watcher that rebuilds on change is a development convenience, and treating it as a deployment pipeline inverts the trust model: anyone who can write to your source directory can change what ships.

The point generalises past this tool. Any build-on-save mechanism has the same property. What makes it worth naming here is that this is a standalone binary people drop into a service host, and a standalone binary that watches and rebuilds looks enough like infrastructure that someone will eventually wire it into a deploy script.

Do not. If you need automatic rebuilds in a deployed environment, that is a pipeline with provenance, not a daemon.

Licence is BSD Two Clause, which is permissive with no copyleft obligation, and the credits name githubnemo as the author with code and documentation contributed by jimstudt.

## Maintenance status, and what to do instead

This is the part that decides the recommendation. The most recent release is v1.4.0 from 2021-09-23, before that 1.3.0 from 2021-05-20, and before that 1.2.0 from 2020-05-02. So the newest tag is five years old.

The last push was on 2026-05-06 and the repository is not archived, so something is still landing, but with no tag since 2021 the distinction between released and unreleased code matters here. `go install github.com/githubnemo/CompileDaemon@latest` resolves to the newest tagged version, not to master.

That pattern is common for small Go utilities. The code is short enough that it keeps working, the toolchain keeps building it, and the maintenance burden is low enough that nobody feels pressure to cut a release. It is not abandoned, and it is not risky for what it does. It is simply not gaining anything.

The practical question is what you would install instead. Go's own toolchain has `go build` and the module system, and most Go IDEs restart on save without an external watcher. If you specifically want a terminal-driven build-and-restart loop with a configurable post-build command and no IDE attached, this does that in a way nothing else does, and the flag table is thorough enough that you can shape it.

If you want that, install it and read the Common Issues section first, because the two failure modes above are the difference between a smooth first run and an afternoon. If you want anything larger, the release history is telling you to look elsewhere.

## Conclusion

Adopt CompileDaemon when you want a local Go build-and-restart loop with no IDE attached, since its defaults, `-directory`, `-recursive` and the default `(.+\.go|.+\.c)$` pattern cover the ordinary case with no configuration. Do not adopt it as a CI build cache or a production rebuild mechanism, because the README itself warns that anyone with write access to your source can inject arbitrary code into a rebuilt binary, and the polling mode it recommends as a Docker workaround is explicitly described as too wasteful for production. Before you start it on a large repository, exclude your version control directories with `-exclude-dir`, because the README documents that watching every file consumes a file descriptor on macOS and BSD and that inotify watcher exhaustion shows up as `filepath.Walk() no space left on device`.

## FAQ

### How do I install and run CompileDaemon?

Install with `go install github.com/githubnemo/CompileDaemon@latest`, then run it with `./CompileDaemon -directory=yourproject/`. With the defaults, running `CompileDaemon` in your source directory recompiles whenever you save a source file.

### What does the -command flag do in CompileDaemon?

It specifies a command to run after a successful build and defaults to nothing. The README's example is `-command="./MyProgram -my-options"`, which keeps a copy of your program running by killing the old one and starting a new one each time you build.

### Why do I get too many open files with CompileDaemon?

On macOS and BSD each watched file consumes a file descriptor, so exclude your version control directories with `-exclude-dir`. If that is not enough, raise the process limit with `ulimit`, for example `ulimit -n 1024`, or adjust the sysctl-based limit.

### What does filepath.Walk() no space left on device mean?

The README explains that it is not disk space. As described in issue 23, you have run out of inotify watchers, which are limited by system configuration, and the fix is to increase the inotify watcher limit. Excluding directories with `-exclude-dir` is the simpler remedy.

### Why should I not use CompileDaemon -polling in production?

The README recommends polling as a workaround for changes on Docker volumes under macOS not being reported, but says it actively watches for changes, naturally uses more CPU resources, and should never be used in production because it is simply too wasteful. The interval is tunable with `-polling-interval=N`.

## Sources

- [githubnemo/CompileDaemon on GitHub](https://github.com/githubnemo/CompileDaemon)
- [Issues](https://github.com/githubnemo/CompileDaemon/issues)
- [License: BSD-2-Clause](https://github.com/githubnemo/CompileDaemon/blob/master/LICENSE)
- [README](https://github.com/githubnemo/CompileDaemon/blob/master/README.md)
- [Releases](https://github.com/githubnemo/CompileDaemon/releases)

---

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