Building Feedback Loops with entr
2025-12-24
Having started my career in web development, I’ve always appreciated the fast feedback loops built into frontend tooling. Hot-reloading makes iteration seamless, but outside web development, such tooling is rare. Here, I show how I use entr to fill those gaps.
A Simple Example
I needed to package a Python script to fix the exported DXF files used for my custom keyboard’s case. However, unfamiliarity with Python packaging in Nix led me to repeatedly context switch between writing code and running:
1nix run .#dxf-fix input.dxf output.dxf
Using entr, I was able to automate this, watching all git-tracked source files for changes and re-running the command automatically:
1git ls-files | entr -c nix run .#dxf-fix input.dxf output.dxf
Note: The -c flag clears the screen before running the command, which helps keep the output readable.
A More Complex Example
Having packaged that Python script, I wanted to automate the end-to-end generation of the keyboard case: extract geometry from the kicad PCB, run it through openscad, then post-process the resulting DXF files with the Python script.
I needed two processes: one to extract changed geometry from the PCB file, and another to build the final case from the extracted files and the OpenSCAD source. The data flow looks like this:
My initial attempt was to have one entr process spawn the other:
1find cweep.kicad_pcb |
2 entr -s '
3 <extract pcb outlines>
4 find case/cweep-*.dxf cweep.scad |
5 entr <build case plates>
6'
But this didn’t work as expected: the inner entr process never exited, so the outer entr process couldn’t rerun extraction when the PCB file changed.
To solve this, I needed two concurrent entr processes:
1find cweep.kicad_pcb |
2 entr -s '<extract pcb outlines>'
3&
4find case/cweep-*.dxf cweep.scad |
5 entr -s '<build case plates>'
However, this introduced new problems: the second process would not wait for the first process to complete, as the first process writes multiple files, triggering the second as soon as the first file was written. Also, stopping this script required pressing Ctrl+C twice, once for each entr process.
For the concurrency problem, I used flock to create a lock file that ensured only one process would run at a time. For the usability problem, I set up a trap to handle SIGINT signals and exit cleanly:
1trap "exit 1" SIGINT
2find cweep.kicad_pcb |
3 entr -s '
4 flock case/.case_gen.lock -c "
5 <extract pcb outlines>
6 "
7 ' &
8find case/cweep-*.dxf cweep.scad |
9 entr -ps '
10 flock case/.case_gen.lock -c "
11 <run case plate builds>
12 "
13 '
14trap - SIGINT
Note: The -p flag for the second entr process prevents it from running the command immediately on startup. In practice, the first process will probably always acquire the lock first, but this avoids relying on timing.
With this setup, I was able to optimize my feedback loop for generating the keyboard case, allowing me to focus on the case design without context switching. If you’re working outside web development and want faster feedback loops, give entr a try!