gastownhall/beads · error

failed to parse projects response: %w

Error message

failed to parse projects response: %w

What it means

Within FetchProjects' pagination loop, each page's JSON body is unmarshalled into ProjectsResponse. This error wraps a json.Unmarshal failure for a page. The library throws it when the response shape does not match {projects:{nodes:[...],pageInfo:{...}}} — decode failure, not a transport failure.

Source

Thrown at internal/linear/client.go:1480

			"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
}

// CreateProject creates a new project in Linear.
func (c *Client) CreateProject(ctx context.Context, name, description, state string) (*Project, error) {
	query := `
		mutation CreateProject($input: ProjectCreateInput!) {
			projectCreate(input: $input) {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Log/dump the raw `data` for the failing page to see the real shape.
  2. Check for a GraphQL "errors" key in the payload and surface its message.
  3. Diff ProjectsResponse/Project/PageInfo structs against the current Linear GraphQL schema and fix drift.
  4. Retry the request directly against the GraphQL endpoint to rule out gateway/proxy body substitution.
  5. Upgrade or pin the client library to match the Linear API version in use.

Example fix

// before
var projectsResp ProjectsResponse
if err := json.Unmarshal(data, &projectsResp); err != nil { return nil, err }
// after
var probe struct{ Projects json.RawMessage `json:"projects"`; Errors json.RawMessage `json:"errors"` }
_ = json.Unmarshal(data, &probe)
if probe.Errors != nil { return nil, fmt.Errorf("graphql error: %s", string(probe.Errors)) }
var projectsResp ProjectsResponse
if err := json.Unmarshal(data, &projectsResp); err != nil { return nil, err }
Defensive patterns

Strategy: type-guard

Type guard

func isProjectsResponse(data []byte) bool {
    var probe struct {
        Projects *struct {
            Nodes    []json.RawMessage `json:"nodes"`
            PageInfo json.RawMessage   `json:"pageInfo"`
        } `json:"projects"`
    }
    return json.Unmarshal(data, &probe) == nil && probe.Projects != nil
}

Try / catch

projects, err := client.FetchProjects(ctx, state)
if err != nil {
    var ute *json.UnmarshalTypeError
    if errors.As(err, &ute) {
        return fmt.Errorf("projects response shape mismatch at field %s — check Linear schema version", ute.Field)
    }
    return err
}

Prevention

When it happens

Trigger: c.Execute returned HTTP-200 data of unexpected shape: a GraphQL errors envelope instead of data, a gateway error page, or Project/ProjectsResponse/PageInfo structs out of sync with the Linear schema (e.g. field renamed or type changed so encoding/json fails).

Common situations: Linear schema change breaking the Project struct; API version mismatch after Linear updates GraphQL; proxy injecting a JSON error object; struct field type mismatch (e.g. progress float vs string).

Understand the failure class

Related errors


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