gastownhall/beads · error

building state cache for team %s: %w

Error message

building state cache for team %s: %w

What it means

Returned by Tracker.BatchPush when BuildStateCache fails for one of the configured teams. BatchPush pre-builds per-team workflow state caches so updates resolve state IDs against the correct team; any team's fetch failure aborts the entire batch.

Source

Thrown at internal/linear/tracker.go:304

// Result mapping: batch-create results are matched by title rather than array
// index, since Linear's API does not guarantee response order matches input order.
func (t *Tracker) BatchPush(ctx context.Context, issues []*types.Issue, forceIDs map[string]bool) (*tracker.BatchPushResult, error) {
	client := t.primaryClient()
	if client == nil {
		return nil, fmt.Errorf("no Linear client available")
	}

	// Build per-team state caches so that updates to issues belonging to different
	// teams resolve workflow state IDs against the correct team's state list.
	teamCaches := make(map[string]*StateCache, len(t.teamIDs))
	for _, teamID := range t.teamIDs {
		teamClient := t.clients[teamID]
		if teamClient == nil {
			continue
		}
		cache, err := BuildStateCache(ctx, teamClient)
		if err != nil {
			return nil, fmt.Errorf("building state cache for team %s: %w", teamID, err)
		}
		teamCaches[teamID] = cache
	}

	// The primary team's cache is used for creates, which always target the primary team.
	primaryCache := teamCaches[t.teamIDs[0]]
	if primaryCache == nil {
		return nil, fmt.Errorf("building state cache: no cache for primary team %s", t.teamIDs[0])
	}

	teamLabelCaches := make(map[string]*LabelCache, len(t.teamIDs))
	for _, teamID := range t.teamIDs {
		teamClient := t.clients[teamID]
		if teamClient == nil {
			continue
		}
		lc, err := BuildLabelCache(ctx, teamClient)
		if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify every team ID in the beads linear config still exists in the Linear workspace
  2. Ensure the API key has access (member/permission) to all configured teams
  3. Retry — transient rate limits or network errors are common with batch operations
  4. Remove or fix stale team entries in config so BatchPush skips missing teams

Example fix

// before
teamIDs = ["ENG", "OLDTEAM"] // OLDTEAM deleted in Linear
// after
teamIDs = ["ENG"] // only live teams configured
Defensive patterns

Strategy: validation

Validate before calling

// Verify all configured teams exist and are accessible before pushing
for _, teamID := range cfg.Linear.TeamIDs {
    if err := verifyTeamAccessible(ctx, apiKey, teamID); err != nil {
        return fmt.Errorf("team %s inaccessible: %w", teamID, err)
    }
}

Try / catch

result, err := tracker.BatchPush(ctx, issues, forceIDs)
if err != nil {
    var teamErr TeamCacheError
    if errors.As(err, &teamErr) || strings.Contains(err.Error(), "building state cache for team") {
        // retry only the affected team's issues
        return retryTeam(teamErr.TeamID, issues)
    }
    return err
}

Prevention

When it happens

Trigger: BatchPush called when the Linear workflow-states query fails for team %s: expired API key lacking that team's access, rate limiting, network error, or the team ID no longer exists in the Linear workspace.

Common situations: Multi-team setups where a team was deleted/renamed in Linear but remains in beads config; API key issued before the team was added or scoped to other teams; transient API outages during large batch syncs.

Related errors


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