{"record":{"id":"6ff4787cc477b8a1","repo":"air-verse/air","slug":"build-rules-d-s-cmd-is-required","errorCode":null,"errorMessage":"build.rules[%d] (%s): cmd is required","messagePattern":"build\\.rules\\[(.+?)\\] \\((.+?)\\): cmd is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"runner/config.go","lineNumber":177,"sourceCode":"\tregexCompiled []*regexp.Regexp\n\tincludeDirAbs []string\n}\n\nfunc (r *cfgRule) delay() time.Duration {\n\tif r.Delay <= 0 {\n\t\treturn 1000 * time.Millisecond\n\t}\n\treturn time.Duration(r.Delay) * time.Millisecond\n}\n\nfunc (c *cfgBuild) normalizeRules(root string) error {\n\tfor i := range c.Rules {\n\t\tr := &c.Rules[i]\n\t\tif r.Name == \"\" {\n\t\t\tr.Name = fmt.Sprintf(\"rule-%d\", i)\n\t\t}\n\t\tif r.Cmd == \"\" {\n\t\t\treturn fmt.Errorf(\"build.rules[%d] (%s): cmd is required\", i, r.Name)\n\t\t}\n\t\tif len(r.IncludeDir) == 0 && len(r.IncludeExt) == 0 && len(r.IncludeFile) == 0 {\n\t\t\treturn fmt.Errorf(\"build.rules[%d] (%s): at least one of include_dir, include_ext or include_file is required\", i, r.Name)\n\t\t}\n\t\tr.includeDirAbs = r.includeDirAbs[:0]\n\t\tfor _, dir := range r.IncludeDir {\n\t\t\tdir = cleanPath(dir)\n\t\t\tif dir == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tabs := filepath.Clean(dir)\n\t\t\tif !filepath.IsAbs(abs) {\n\t\t\t\tabs = filepath.Join(root, abs)\n\t\t\t}\n\t\t\tr.includeDirAbs = append(r.includeDirAbs, filepath.Clean(abs))\n\t\t}\n\t\tr.regexCompiled = r.regexCompiled[:0]\n\t\tfor _, expr := range r.ExcludeRegex {","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/air-verse/air/blob/71ea1dee05248122ed22b5870c7fb20a90d80ddd/runner/config.go#L159-L195","documentation":"Config validation (normalizeRules) requires every entry in `build.rules` to define a `cmd`; a rule with an empty `cmd` fails validation with its index and name. Rules without cmd cannot run anything when their file matches.","triggerScenarios":"Loading a `.air.toml` whose `[[build.rules]]` block omits `cmd` or sets `cmd = \"\"`, during config initialization.","commonSituations":"Copy-pasting a rules example and deleting the cmd line; adding a rule placeholder intending to fill in cmd later; a TOML formatting mistake that leaves cmd unset.","solutions":["Add a cmd to the rule: cmd = \"make generate\"","Remove the empty [[build.rules]] block if unused","Give the rule an explicit name too, so future errors point at it"],"exampleFix":"// before (.air.toml)\n[[build.rules]]\nname = \"codegen\"\ninclude_ext = [\"go\"]\n// after (.air.toml)\n[[build.rules]]\nname = \"codegen\"\ncmd = \"go generate ./...\"\ninclude_ext = [\"go\"]","handlingStrategy":"validation","validationCode":"# before starting air, check each rules block has cmd\ngrep -A5 '^\\\\[\\\\[build.rules\\\\]\\\\]' .air.toml | grep -q 'cmd =' || echo \"rule missing cmd\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set cmd in every [[build.rules]] block","Give each rule a name so validation errors are easy to locate","Review rules after copy-pasting examples"],"tags":["config","validation","rules","toml"],"backgroundTag":"missing-required-config-field","analyzedSha":"71ea1dee05248122ed22b5870c7fb20a90d80ddd","analyzedAt":"2026-08-31T20:27:54.676Z","schemaVersion":2},"datasetVersion":"2026-09-01T03:17:15.561Z"}