gastownhall/beads · error

failed to fetch projects: %w

Error message

failed to fetch projects: %w

What it means

FetchProjects pages through Linear projects (projectsQuery with team filter, optional state, first: MaxPageSize, cursor pagination). This error wraps any Execute failure on a page fetch: network error, HTTP error, GraphQL error, or context cancellation. Because it runs in a pagination loop, a failure on any page aborts the whole listing.

Source

Thrown at internal/linear/client.go:1475

	}

	for {
		variables := map[string]interface{}{
			"filter": filter,
			"first":  MaxPageSize,
		}
		if cursor != "" {
			variables["after"] = cursor
		}

		req := &GraphQLRequest{
			Query:     projectsQuery,
			Variables: variables,
		}

		data, err := c.Execute(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("failed to fetch projects: %w", err)
		}

		var projectsResp ProjectsResponse
		if err := json.Unmarshal(data, &projectsResp); err != nil {
			return nil, fmt.Errorf("failed to parse projects response: %w", err)
		}

		allProjects = append(allProjects, projectsResp.Projects.Nodes...)

		if !projectsResp.Projects.PageInfo.HasNextPage {
			break
		}
		cursor = projectsResp.Projects.PageInfo.EndCursor
	}

	return allProjects, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to find the underlying cause (transport vs GraphQL vs context).
  2. Validate c.TeamID against FetchTeams — a wrong team ID causes GraphQL errors on every page.
  3. Check the state argument is one of planned/started/paused/completed/canceled (or "all"/"").
  4. Implement retry-with-backoff per page and reuse the last good cursor so you don't restart from page 1.
  5. Watch for 429s in the wrapped error and slow the request rate (Linear rate limits).

Example fix

// before
projects, err := client.FetchProjects(ctx, "active") // invalid state value
// after
projects, err := client.FetchProjects(ctx, "started") // planned|started|paused|completed|canceled|all|""
Defensive patterns

Strategy: retry

Validate before calling

// validate state argument and team id before the paginated call
validStates := map[string]bool{"planned":true,"started":true,"paused":true,"completed":true,"canceled":true,"all":true,"":true}
if !validStates[state] {
    return fmt.Errorf("invalid project state %q", state)
}
if client.TeamID == "" {
    return fmt.Errorf("TeamID not configured")
}

Try / catch

projects, err := client.FetchProjects(ctx, "started")
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
        defer cancel()
        return client.FetchProjects(ctx, "started") // read-only: safe to retry
    }
    return fmt.Errorf("list projects: %w", err)
}

Prevention

When it happens

Trigger: c.Execute fails mid-pagination: token lacks project read scope, team filter references a team ID the token can't see, invalid `state` filter value, rate limit on a large workspace, network blip between pages, or ctx deadline exceeded during a long crawl.

Common situations: Listing projects on a big workspace hits 429s mid-loop; c.TeamID configured from another workspace; using a state string outside planned/started/paused/completed/canceled; long-running sync whose context times out on page 3+.

Related errors


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