gastownhall/beads · error

agents file must be a simple filename without path separator

Error message

agents file must be a simple filename without path separators, got %q

What it means

ValidateAgentsFile requires a plain filename: any '/' or '\\' means the caller passed a path rather than a simple name. This restriction lets the write layer control where agents files live and simplifies symlink/security checks. The offending name is quoted in the message.

Source

Thrown at internal/config/config.go:1176

		debug.Logf("config: agents.file %q failed validation (%v), using default", name, err)
		return DefaultAgentsFile
	}
	return name
}

// ValidateAgentsFile checks that filename is safe to use as an agents file path.
// It rejects absolute paths, path separators, names longer than 255 characters,
// and non-markdown extensions. This is a pure string validation function — I/O
// checks (e.g. symlink detection) are deferred to the file write layer.
func ValidateAgentsFile(filename string) error {
	if filename == "" {
		return fmt.Errorf("agents file name must not be empty")
	}
	if len(filename) > 255 {
		return fmt.Errorf("agents file name exceeds 255 characters")
	}
	if strings.ContainsAny(filename, "/\\") {
		return fmt.Errorf("agents file must be a simple filename without path separators, got %q", filename)
	}
	ext := strings.ToLower(filepath.Ext(filename))
	if ext != ".md" {
		return fmt.Errorf("agents file must have .md extension, got %q", ext)
	}
	return nil
}

// getConfigList retrieves a list-typed configuration value from config.yaml,
// accepting either the YAML list form (e.g. `types: { custom: [step, wisp] }`)
// or the legacy comma-separated string form (e.g.
// `types.custom = "step,wisp"`). Entries are trimmed; empty entries are
// dropped. The dual-form support is required for project-extension
// types/statuses declared in .beads/config.yaml — see gastownhall/beads#4024.
func getConfigList(key string) []string {
	if v == nil {
		debug.Logf("config: viper not initialized, returning nil for key %q", key)
		return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass only the base filename, e.g. "AGENTS.md", and let the library place it in the beads directory.
  2. Use filepath.Base on the configured value at the call site before validation.
  3. Update the config key to remove any directory portion.

Example fix

// before
config.SafeAgentsFile("docs/AGENTS.md")
// after
config.SafeAgentsFile(filepath.Base("docs/AGENTS.md")) // "AGENTS.md"
Defensive patterns

Strategy: validation

Validate before calling

if strings.ContainsAny(name, "/\\") {
    return fmt.Errorf("pass a bare filename, not a path: %q", name)
}
name = filepath.Base(name) // normalize before calling the library

Try / catch

if err := config.SafeAgentsFile(name); err != nil {
    if strings.Contains(err.Error(), "path separators") {
        log.Warn("agents file must be a bare name; using base", "got", name)
        return config.SafeAgentsFile(filepath.Base(name))
    }
    return err
}

Prevention

When it happens

Trigger: Calling ValidateAgentsFile or SafeAgentsFile with values like "docs/AGENTS.md", "/etc/agents.md", "sub\\dir.md", or any name containing a path separator.

Common situations: A user sets an absolute path in the agents-file config key; a script builds a relative path; Windows-style separators leak in from cross-platform tooling.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4b418a9b4f49e713. Report an issue: GitHub.