gastownhall/beads · error

commands %q and %q both map to doc page %s.md; rename one

Error message

commands %q and %q both map to doc page %s.md; rename one

What it means

Returned by writeGenericCLIDocsDir when two distinct command names collapse to the same doc page ID via commandDocID (which strips punctuation). This prevents one page silently overwriting the other.

Source

Thrown at cmd/bd/help_all.go:329

	if err := os.MkdirAll(outDir, 0o755); err != nil {
		return err
	}
	if err := removeMarkdownFiles(outDir); err != nil {
		return err
	}

	commands := availableCommandNames(root)
	if err := writeMarkdownFile(filepath.Join(outDir, "index.md"), genericCLIReferenceIndex(root, commands)); err != nil {
		return err
	}

	// commandDocID collapses punctuation, so distinct command names can map
	// to the same page file; the later write would silently win.
	seen := make(map[string]string, len(commands))
	for _, name := range commands {
		id := commandDocID(name)
		if prev, ok := seen[id]; ok {
			return fmt.Errorf("commands %q and %q both map to doc page %s.md; rename one", prev, name, id)
		}
		seen[id] = name

		var out bytes.Buffer
		if err := writeSingleCommandDoc(&out, root, name); err != nil {
			return err
		}
		path := filepath.Join(outDir, id+".md")
		if err := writeMarkdownFile(path, out.String()); err != nil {
			return err
		}
	}

	return nil
}

func genericCLIReferenceIndex(root *cobra.Command, commands []string) string {
	var b strings.Builder

View on GitHub (pinned to 71377f2769)

Solutions

  1. Rename one of the commands in the docs list so IDs differ, as the message advises.
  2. Remove the duplicate/alias entry from the commands list if both spellings refer to one command.
  3. If the collision is inherent, change commandDocID's collapsing rules deliberately (with a test).

Example fix

// before: collision
commands := []string{"mol pour", "mol-pour"}
// after
commands := []string{"mol pour"}
Defensive patterns

Strategy: validation

Validate before calling

ids := map[string]string{}
for _, name := range commands {
	id := commandDocID(name)
	if prev, ok := ids[id]; ok { return fmt.Errorf("doc id collision: %s vs %s", prev, name) }
	ids[id] = name
}

Try / catch

if err := writeGenericCLIDocsDir(...); err != nil {
	if strings.Contains(err.Error(), "both map to doc page") {
		// dedupe command list and retry
	}
}

Prevention

When it happens

Trigger: The commands list passed to writeGenericCLIDocsDir contains two names whose commandDocID values are equal (e.g. 'mol pour' and 'mol-pour' or 'mol.pour' both mapping to 'mol-pour.md'); the second hit of a seen id triggers the error.

Common situations: Adding a new alias or renamed command to the docs list without removing the old spelling, punctuation-only differences between command names, or duplicate entries in a generated commands list.

Related errors


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