{"record":{"id":"0eb151ad1e4f0cd2","repo":"gastownhall/beads","slug":"agents-file-must-have-md-extension-got-q","errorCode":null,"errorMessage":"agents file must have .md extension, got %q","messagePattern":"agents file must have \\.md extension, got %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/config.go","lineNumber":1180,"sourceCode":"}\n\n// ValidateAgentsFile checks that filename is safe to use as an agents file path.\n// It rejects absolute paths, path separators, names longer than 255 characters,\n// and non-markdown extensions. This is a pure string validation function — I/O\n// checks (e.g. symlink detection) are deferred to the file write layer.\nfunc ValidateAgentsFile(filename string) error {\n\tif filename == \"\" {\n\t\treturn fmt.Errorf(\"agents file name must not be empty\")\n\t}\n\tif len(filename) > 255 {\n\t\treturn fmt.Errorf(\"agents file name exceeds 255 characters\")\n\t}\n\tif strings.ContainsAny(filename, \"/\\\\\") {\n\t\treturn fmt.Errorf(\"agents file must be a simple filename without path separators, got %q\", filename)\n\t}\n\text := strings.ToLower(filepath.Ext(filename))\n\tif ext != \".md\" {\n\t\treturn fmt.Errorf(\"agents file must have .md extension, got %q\", ext)\n\t}\n\treturn nil\n}\n\n// getConfigList retrieves a list-typed configuration value from config.yaml,\n// accepting either the YAML list form (e.g. `types: { custom: [step, wisp] }`)\n// or the legacy comma-separated string form (e.g.\n// `types.custom = \"step,wisp\"`). Entries are trimmed; empty entries are\n// dropped. The dual-form support is required for project-extension\n// types/statuses declared in .beads/config.yaml — see gastownhall/beads#4024.\nfunc getConfigList(key string) []string {\n\tif v == nil {\n\t\tdebug.Logf(\"config: viper not initialized, returning nil for key %q\", key)\n\t\treturn nil\n\t}\n\n\t// Try the YAML-list form first. Viper's GetStringSlice returns:\n\t//   * []string for a YAML sequence value,","sourceCodeStart":1162,"sourceCodeEnd":1198,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/config/config.go#L1162-L1198","documentation":"ValidateAgentsFile enforces that agents files use the .md extension (case-insensitive), since they are markdown documents consumed by agents tooling. Any other extension is rejected with the actual extension quoted via %q.","triggerScenarios":"Calling ValidateAgentsFile or SafeAgentsFile with names like \"AGENTS.txt\", \"AGENTS.markdown\", \"AGENTS\" (no extension), or \"AGENTS.MD\" handled by a call site that checks case-sensitively elsewhere (validation itself lowercases, so .MD passes).","commonSituations":"A user configures their existing notes file (.txt) as the agents file; tooling defaults to .txt; a template writes a different extension.","solutions":["Rename the file to end in .md (extensions are compared case-insensitively, so .MD works).","Convert the content to Markdown if it was plain text.","Update the config value to a name ending in .md."],"exampleFix":"// before\nconfig.SafeAgentsFile(\"AGENTS.txt\")\n// after\nconfig.SafeAgentsFile(\"AGENTS.md\")","handlingStrategy":"validation","validationCode":"if strings.ToLower(filepath.Ext(name)) != \".md\" {\n    return fmt.Errorf(\"agents file must be .md, got %q\", filepath.Ext(name))\n}","typeGuard":null,"tryCatchPattern":"if err := config.SafeAgentsFile(name); err != nil {\n    if strings.Contains(err.Error(), \".md extension\") {\n        fixed := strings.TrimSuffix(name, filepath.Ext(name)) + \".md\"\n        return config.SafeAgentsFile(fixed)\n    }\n    return err\n}","preventionTips":["Standardize on .md for all agent-facing documents in your repo.","Add a config lint rule checking the agents-file key ends with .md (case-insensitive).","When migrating notes files, convert format and extension together.","Keep templates and generators emitting .md names."],"tags":["config","validation","filename","file-extension"],"backgroundTag":"invalid-file-extension","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}