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
- Run the Linear sync/pull first so the StateCache is populated with workflow states.
- Verify the configured Linear team actually has workflow states (default teams do).
- Check that the state cache file exists and is not empty/corrupted; re-sync if in doubt.
- Pass a non-nil *StateCache to ResolveStateIDForBeadsStatus at the call site.
- 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
- Always sync/pull from Linear before the first push in a new environment.
- Check the configured team has workflow states before integrating.
- Validate the cache file is present and non-empty at startup.
- Run 'bd linear link' during onboarding to bootstrap the cache.
- Guard resolver call sites with an explicit cache-populated check.
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
- %s
- linear.outbound_state_map.%s = %q does not match any Linear
- linear.state_map maps beads status %q to multiple Linear sta
- linear.state_map type fallback is ambiguous for beads status
- linear.state_map has no configured Linear state for beads st
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c76d91ce84fd1fa1.
Report an issue: GitHub.