{"record":{"id":"497f3b5c43552849","repo":"gastownhall/beads","slug":"close-reason-is-empty-or-default-provide-a-summar","errorCode":null,"errorMessage":"close reason is empty or default; provide a summary of what was done","messagePattern":"close reason is empty or default; provide a summary of what was done","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/validation/template.go","lineNumber":208,"sourceCode":"\t\theading := strings.ToLower(strings.TrimPrefix(m.Heading, \"## \"))\n\t\tif heading == \"acceptance criteria\" || heading == \"success criteria\" {\n\t\t\tcontinue // satisfied by the dedicated field\n\t\t}\n\t\tremaining = append(remaining, m)\n\t}\n\tif len(remaining) == 0 {\n\t\treturn nil\n\t}\n\ttemplateErr.Missing = remaining\n\treturn templateErr\n}\n\n// ValidateCloseReason checks if a close reason meets minimum quality standards.\n// Returns nil if the reason is acceptable. Used by validation.on-close config.\nfunc ValidateCloseReason(reason string) error {\n\treason = strings.TrimSpace(reason)\n\tif reason == \"\" || strings.EqualFold(reason, \"closed\") {\n\t\treturn fmt.Errorf(\"close reason is empty or default; provide a summary of what was done\")\n\t}\n\tif len(reason) < 20 {\n\t\treturn fmt.Errorf(\"close reason is terse (%d chars); aim for 20+ characters describing what was done\", len(reason))\n\t}\n\treturn nil\n}\n","sourceCodeStart":190,"sourceCodeEnd":215,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/validation/template.go#L190-L215","documentation":"ValidateCloseReason refuses close reasons that are empty or exactly the word \"closed\" (case-insensitive). The library requires a human-meaningful summary of what was done when closing an issue, because validation.on-close config enforces close-reason quality. A bare or default reason carries no audit value.","triggerScenarios":"Calling bd close (or any path that invokes ValidateCloseReason via validation.on-close) with an empty --reason flag, whitespace-only input, or the literal string \"closed\"/\"Closed\".","commonSituations":"Scripts and agents closing issues programmatically with bd close without --reason; interactive users accepting a default close message; CI automations that forgot the reason field.","solutions":["Pass a descriptive reason with the close command, e.g. bd close bd-42 --reason \"Fixed by normalizing prefix in bootstrap; covered by tests\"","If closing many issues in a script, template a reason that names the commit or change","Remove or relax the validation.on-close config only if your team genuinely does not require close summaries"],"exampleFix":"// before\nbd close bd-42 --reason \"closed\"\n// after\nbd close bd-42 --reason \"Fixed flaky TestSync by retrying dolt push; see commit ab12cd\"","handlingStrategy":"validation","validationCode":"func validCloseReason(r string) bool {\n    r = strings.TrimSpace(r)\n    return r != \"\" && !strings.EqualFold(r, \"closed\") && len(r) >= 20\n}\n// call: if !validCloseReason(reason) { reason = buildDescriptiveReason() }","typeGuard":null,"tryCatchPattern":"if err := validation.ValidateCloseReason(reason); err != nil {\n    if strings.Contains(err.Error(), \"empty or default\") { reason = promptForReason() }\n    return err\n}","preventionTips":["Always pass --reason with bd close; never rely on defaults","Ban the literal \"closed\" as a reason in scripts and templates","Enforce a minimum reason length in your own tooling before calling close"],"tags":["validation","close-reason","issue-tracking"],"backgroundTag":"close-reason-quality","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}