根本原因是工作目录或配置路径错误导致air未监控到修改文件;需确认在go.mod所在根目录运行、检查air.toml中root/build.bin/watch字段配置正确,并注意Windows文件锁、热重启非热加载等限制。

Go 项目用 air 热重启,为什么改了代码没反应?
根本原因通常是工作目录或配置路径没对上,air 没监控到你正在编辑的文件。它默认只监听当前目录下的 .go 文件,且不递归扫描符号链接、隐藏目录(如 .git)或 vendor 目录——但如果你把代码放在子模块或 internal/ 里,又没显式配置,就容易“静默失效”。
- 运行前先确认你在项目根目录(即
go.mod所在路径),执行air而不是air -c air.toml却忘了放配置文件 - 检查
air.toml中的root字段是否写死了绝对路径,导致切换机器或用户后失效;建议留空或用. -
air默认忽略tmp/、vendor/、.git/,但如果你把 handler 放在cmd/myapp/main.go,而air.toml的build.bin指向了错误的main包路径,编译会成功但启动的是旧二进制
air.toml 必配项:哪些字段改了才真正影响热更新行为
很多人抄来一份配置就跑,但实际只有几个字段决定“改哪类文件会触发重建+重启”,其余只是优化体验。重点不是全填,而是填对。
-
build.cmd:必须是能直接运行的构建命令,比如"go build -o ./tmp/main .";不能写成go run main.go,否则air无法接管进程生命周期,改完只会打印日志却不重启 -
build.bin:要和build.cmd输出路径严格一致,例如./tmp/main,否则air启动时找不到可执行文件,报错exec: "./tmp/main": file does not exist -
watch.cwd和watch.extensions:前者决定从哪个目录开始扫描,后者控制监听哪些后缀,默认不含.tmpl或.yaml;如果项目用模板渲染 HTML,得手动加进去,否则改了模板不触发重启
Windows 下 air 报 fork/exec 或权限拒绝?
这不是 Go 编译问题,而是 Windows 对可执行文件锁死机制导致的:旧进程还没完全退出,新二进制就被覆盖,系统拒绝写入。尤其在快速保存多次时高频出现。
- 别用
go build -o main.exe .直接覆盖同名文件;改成带时间戳或随机后缀,比如go build -o ./tmp/main_$(date +%s).exe .(Windows 命令行需换为for /f %i in ('powershell -command "Get-Date -UFormat %s"') do go build -o ./tmp/main_%i.exe .) - 更稳的方式是启用
air的build.delay(单位毫秒),设为500以上,给旧进程释放句柄留出缓冲 - 某些杀毒软件会拦截
air启动的子进程,临时禁用实时防护可验证是否为此原因
热更新 ≠ 热加载:HTTP Server 重启时连接中断怎么缓解?
air 做的是进程级重启,不是 Goroutine 级热替换。每次构建完成都会 kill 旧进程、start 新进程,中间存在毫秒级不可用窗口。对开发阶段的 API 调试影响不大,但如果你正用浏览器长连 SSE 或 WebSocket,就会断开。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖
air实现无感更新;真要保连接,得在应用层加 graceful shutdown:监听SIGUSR2(Linux/macOS)或用第三方库如fvbock/endless,但air默认不发该信号,需配合自定义build.cmd脚本 - 开发时更实际的做法是:前端用
fetch加重试逻辑,后端接口响应头加Connection: close明确告知客户端别复用连接 - 注意
http.Server的Shutdown()超时时间别设太短(如5 * time.Second),否则可能还没等活跃请求结束就被强制 kill
事情说清了就结束。热重启本身很简单,难的是让它的行为和你的项目结构、操作系统、IDE 保存习惯严丝合缝。多数问题不在 air 本身,而在路径、权限、进程模型这些“看不见的胶水”。










