air-go
Air — Go Hot Reload
Air watches Go source files and rebuilds/restarts your app on changes. Essential for web server development where you want sub-second feedback.
Install
go install github.com/air-verse/air@latest
Basic usage
Run air in your project root. With no config, it watches .go files,
rebuilds, and restarts. For anything non-trivial, use a .air.toml.
.air.toml
root = "."
tmp_dir = "tmp"
[build]
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 500
exclude_dir = ["tmp", "k8s", "cmd", "node_modules", "vendor"]
exclude_regex = ["_test\\.go$"]
include_ext = ["go", "html", "css", "js", "yml", "yaml", "toml"]
kill_delay = "1s"
send_interrupt = true
stop_on_error = true
[misc]
clean_on_exit = true
Key settings explained
| Setting | What it does | Why it matters |
|---|---|---|
delay |
Milliseconds to wait after a file change before rebuilding | Prevents rapid-fire rebuilds when saving multiple files |
kill_delay |
Time to wait after killing the old process before starting the new one | Critical for SQLite/file locks — the old process needs time to release resources |
send_interrupt |
Send SIGINT before SIGKILL | Allows graceful shutdown (flush writes, close DB connections) |
stop_on_error |
Don't restart on build errors | Prevents crash loops when you have a syntax error |
include_ext |
File extensions to watch | Add html, css, js to reload on frontend changes too |
exclude_dir |
Directories to ignore | Exclude test fixtures, k8s manifests, vendored deps |
exclude_regex |
File patterns to ignore | Skip test files to avoid rebuilding when only tests change |
clean_on_exit |
Remove tmp dir on exit | Prevents stale binaries from confusing the next run |
Makefile integration
.PHONY: dev dev-email dev-clean
# Dev: console output, fresh state
dev: dev-clean
air
# Dev: real emails, LAN-accessible for mobile testing
dev-email: dev-clean
$(call setup_env, .env.prod)
SMTP_HOST=smtp.example.com \
BASE_URL=http://$(LAN_IP):8080 \
air
dev-clean:
@rm -f app.db app.db-shm app.db-wal tmp/main
@-lsof -ti :8080 | xargs kill -9 2>/dev/null || true
The dev-clean target is important — it kills anything on the port and removes
stale DB files before starting fresh.
Passing environment variables
Air inherits the parent shell's environment. Set env vars inline before air:
# Inline
PORT=3000 DEBUG=true air
# From .env file in Makefile
$(call setup_env, .env.dev)
air
Do NOT put env vars in .air.toml — it doesn't support that. Environment is
always from the shell.
Common issues and fixes
"address already in use"
The previous process didn't die. Fix:
lsof -ti :8080 | xargs kill -9
Or add to dev-clean target as shown above.
"database is locked" (SQLite)
Air kills the old process and starts the new one, but SQLite WAL files can
linger. Fix with these .air.toml settings:
[build]
kill_delay = "1s" # Give old process time to release the lock
send_interrupt = true # Graceful shutdown via SIGINT
pre_cmd = ["rm -f app.db-shm app.db-wal"] # Clean stale lock files
If using an in-memory DB or ephemeral dev DB, just delete it on each rebuild:
[build]
pre_cmd = ["rm -f app.db app.db-shm app.db-wal"]
"too many open files" on large projects
Air's file watcher can hit OS limits. Exclude unnecessary directories:
[build]
exclude_dir = ["tmp", "vendor", "node_modules", ".git", "k8s", "docs"]
Air exits immediately in background/CI
Air is a foreground tool — it watches stdin for Ctrl+C. In scripts or CI, use
go run . instead. Air is for interactive development only.
Changes not detected
Check that your file extension is in include_ext and the directory isn't in
exclude_dir. Air only watches extensions you explicitly list.
Watching non-Go files
To rebuild on HTML/CSS/JS changes (useful for apps that serve static files):
[build]
include_ext = ["go", "html", "css", "js"]
Air will rebuild the Go binary even when only frontend files change. This is fine — Go builds are fast and the restart picks up the new static files.
Multi-binary projects
If your project has multiple binaries (cmd/server, cmd/worker), point cmd
at the one you want:
[build]
cmd = "go build -o ./tmp/server ./cmd/server"
bin = "./tmp/server"
Run a second air instance in another terminal for the worker if needed, with a separate config:
air -c .air.worker.toml
More from brojonat/llmsrules
ibis-data
Use Ibis for database-agnostic data access in Python. Use when writing data queries, connecting to databases (DuckDB, PostgreSQL, SQLite), or building portable data pipelines that should work across backends.
13go-service
Build Go microservices with stdlib HTTP handlers, sqlc, urfave/cli, and slog. Use when creating or modifying a Go HTTP server, adding routes, middleware, database queries, or CLI commands.
13temporal-go
Build Temporal workflow applications in Go. Use when creating or modifying Temporal workflows, activities, workers, clients, signals, queries, updates, retry policies, saga patterns, or writing Temporal tests.
13parquet-analysis
Analyze parquet files using Python and Ibis. Use when the user wants to explore, transform, or analyze parquet data files, perform aggregations, joins, or export results. Works with local parquet files and provides database-agnostic data operations.
12ducklake
Work with DuckLake, an open lakehouse format built on DuckDB. Use when creating or querying DuckLake tables, managing snapshots, time travel, schema evolution, partitioning, or lakehouse maintenance operations.
12temporal-python
Build Temporal applications in Python using the temporalio SDK. Use when creating workflows, activities, workers, clients, signals, queries, updates, child workflows, timers, retry policies, saga/compensation patterns, testing, or any durable execution pattern in Python.
12