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
- Unwrap the error to find the underlying cause (transport vs GraphQL vs context).
- Validate c.TeamID against FetchTeams — a wrong team ID causes GraphQL errors on every page.
- Check the state argument is one of planned/started/paused/completed/canceled (or "all"/"").
- Implement retry-with-backoff per page and reuse the last good cursor so you don't restart from page 1.
- 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
- Use a per-call context timeout generous enough for multi-page crawls of large workspaces.
- Validate TeamID with FetchTeams during configuration.
- Restrict state to the documented enum values.
- Add backoff on 429s; pagination loops amplify rate limiting.
- Treat the call as idempotent (read-only) — safe to retry from scratch.
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
- failed to fetch issue by identifier: %w
- failed to fetch teams: %w
- failed to parse update response: %w
- batch create failed and recovery search also failed: %w (bat
- batch create failed; %d of %d issues unconfirmed (batch erro
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/82cdb01ff1a071cd.
Report an issue: GitHub.