gastownhall/beads · error

no workflow states found

Error message

no workflow states found

What it means

ResolveStateIDForBeadsStatus returns this when the StateCache is nil or contains no Linear workflow states, so there is nothing to resolve a target state ID against. It guards push operations from mutating Linear without a populated state cache.

Source

Thrown at internal/linear/mapping.go:377

	if parsed == status {
		// ParseBeadsStatus returns StatusOpen for unrecognized strings; do not
		// treat those as matching built-in open (avoids false "ambiguous mapping"
		// when state_map values are custom status names like "review").
		if parsed == types.StatusOpen && normalizedMapped != "open" {
			return false
		}
		return true
	}
	return false
}

// ResolveStateIDForBeadsStatus returns the unique Linear workflow state ID to
// use when pushing the given beads status. Push only trusts explicit
// linear.state_map.* entries; defaults are safe for pull but too ambiguous for
// mutation.
func ResolveStateIDForBeadsStatus(cache *StateCache, status types.Status, config *MappingConfig) (string, error) {
	if cache == nil || len(cache.States) == 0 {
		return "", fmt.Errorf("no workflow states found")
	}
	if config == nil || len(config.ExplicitStateMap) == 0 {
		return "", fmt.Errorf("%s", missingExplicitStateMapMessage)
	}

	// Outbound override: an explicit linear.outbound_state_map.<status> entry
	// names the exact Linear workflow state to push to and short-circuits the
	// name/type matching below. This is the escape hatch when multiple Linear
	// states share a type (e.g. "In Progress" and "In Review" are both
	// "started") and the type-based fallback would otherwise be ambiguous.
	if outboundName, ok := config.OutboundStateMap[strings.ToLower(strings.TrimSpace(string(status)))]; ok {
		want := strings.ToLower(strings.TrimSpace(outboundName))
		for _, state := range cache.States {
			if strings.ToLower(strings.TrimSpace(state.Name)) == want {
				return state.ID, nil
			}
		}
		return "", fmt.Errorf("linear.outbound_state_map.%s = %q does not match any Linear workflow state", status, outboundName)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run the Linear sync/pull first so the StateCache is populated with workflow states.
  2. Verify the configured Linear team actually has workflow states (default teams do).
  3. Check that the state cache file exists and is not empty/corrupted; re-sync if in doubt.
  4. Pass a non-nil *StateCache to ResolveStateIDForBeadsStatus at the call site.
  5. Run 'bd linear link' to (re)initialize the Linear integration.

Example fix

// before
stateID, err := linear.ResolveStateIDForBeadsStatus(cache, status, cfg) // cache may be nil
// after
if cache == nil || len(cache.States) == 0 {
	return fmt.Errorf("state cache empty; run sync to fetch Linear workflow states first")
}
stateID, err := linear.ResolveStateIDForBeadsStatus(cache, status, cfg)
Defensive patterns

Strategy: validation

Validate before calling

func requireStateCache(cache *linear.StateCache) error {
	if cache == nil {
		return fmt.Errorf("state cache not loaded; run sync first")
	}
	if len(cache.States) == 0 {
		return fmt.Errorf("state cache empty; re-run Linear sync to fetch workflow states")
	}
	return nil
}

Type guard

func hasStates(cache *linear.StateCache) bool {
	return cache != nil && len(cache.States) > 0
}

Try / catch

stateID, err := linear.ResolveStateIDForBeadsStatus(cache, status, cfg)
if err != nil {
	if strings.Contains(err.Error(), "no workflow states found") {
		if err := syncLinearStates(ctx, client); err != nil {
			return fmt.Errorf("state cache empty and re-sync failed: %w", err)
		}
		stateID, err = linear.ResolveStateIDForBeadsStatus(cache, status, cfg)
	}
	if err != nil {
		return err
	}
}

Prevention

When it happens

Trigger: Calling ResolveStateIDForBeadsStatus with cache == nil or cache.States empty — e.g. before any workflow-state sync ran, or after a sync that fetched zero states (empty team, wrong team scoping).

Common situations: Running 'bd push' to Linear before the initial pull/sync populated the cache; the configured Linear team has no workflow states; a corrupted or wiped cache file.

Related errors


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