gastownhall/beads · error

linear.state_map maps beads status %q to multiple Linear sta

Error message

linear.state_map maps beads status %q to multiple Linear states: %s

What it means

Multiple Linear workflow states have explicit linear.state_map entries that both match the beads status being pushed (matched by state name), so the resolver cannot pick a unique target and refuses to mutate. Pushes require an unambiguous mapping.

Source

Thrown at internal/linear/mapping.go:413

		return "", fmt.Errorf("linear.outbound_state_map.%s = %q does not match any Linear workflow state", status, outboundName)
	}

	var nameMatches []State
	for _, state := range cache.States {
		mapped, ok := config.ExplicitStateMap[strings.ToLower(strings.TrimSpace(state.Name))]
		if ok && stateMapMatchesStatus(mapped, status) {
			nameMatches = append(nameMatches, state)
		}
	}
	if len(nameMatches) == 1 {
		return nameMatches[0].ID, nil
	}
	if len(nameMatches) > 1 {
		names := make([]string, 0, len(nameMatches))
		for _, state := range nameMatches {
			names = append(names, state.Name)
		}
		return "", fmt.Errorf("linear.state_map maps beads status %q to multiple Linear states: %s", status, strings.Join(names, ", "))
	}

	var typeMatches []State
	for _, state := range cache.States {
		mapped, ok := config.ExplicitStateMap[strings.ToLower(strings.TrimSpace(state.Type))]
		if ok && stateMapMatchesStatus(mapped, status) {
			typeMatches = append(typeMatches, state)
		}
	}
	if len(typeMatches) == 1 {
		return typeMatches[0].ID, nil
	}
	if len(typeMatches) > 1 {
		names := make([]string, 0, len(typeMatches))
		for _, state := range typeMatches {
			names = append(names, state.Name)
		}
		return "", fmt.Errorf("linear.state_map type fallback is ambiguous for beads status %q across Linear states: %s. Set linear.outbound_state_map.%s = \"<state name>\" to disambiguate", status, strings.Join(names, ", "), status)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Map each beads status to at most one Linear state name in linear.state_map.
  2. Use linear.outbound_state_map.<status> = "<state name>" to pin the push target for that status, which takes precedence.
  3. Remove the redundant/incorrect state_map entry that creates the duplicate match.
  4. Re-run 'bd linear link' to regenerate a one-to-one mapping.

Example fix

// before
[linear.state_map]
todo = "open"
backlog = "open"   # ambiguous: two names map to 'open'
// after
[linear.state_map]
backlog = "open"
[linear.outbound_state_map]
open = "Backlog"   # explicit push target disambiguates
Defensive patterns

Strategy: validation

Validate before calling

func checkNameAmbiguity(cfg *linear.MappingConfig, cache *linear.StateCache) error {
	for status := range cfg.OutboundStateMap {
		matches := 0
		for _, s := range cache.States {
			if mapped, ok := cfg.ExplicitStateMap[strings.ToLower(strings.TrimSpace(s.Name))]; ok && mapped == status {
				matches++
			}
		}
		if matches > 1 {
			return fmt.Errorf("status %q matches multiple states by name; set outbound_state_map", status)
		}
	}
	return nil
}

Try / catch

stateID, err := linear.ResolveStateIDForBeadsStatus(cache, status, cfg)
if err != nil && strings.Contains(err.Error(), "multiple Linear states") {
	return fmt.Errorf("ambiguous push mapping for %s: set linear.outbound_state_map.%s", status, status)
}

Prevention

When it happens

Trigger: Calling ResolveStateIDForBeadsStatus where nameMatches (states whose explicit linear.state_map.<state-name> entry matches the status) contains 2+ states — e.g. linear.state_map.'Todo'='open' and linear.state_map.'Backlog'='open'.

Common situations: Configuring several Linear states (e.g. 'Todo' and 'Backlog') to the same beads status 'open'; copy-pasting state_map blocks for every state in the workspace.

Related errors


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