gastownhall/beads · error

failed to parse description search response

Error message

failed to parse description search response: %w

What it means

Once the description-search query succeeds, the response bytes are unmarshalled into IssuesResponse. This error wraps a JSON unmarshal failure, meaning the HTTP call worked but the body does not match the expected `{ issues: { nodes: [...] } }` shape.

Solutions

  1. Log the raw response body alongside the unmarshal error to see the actual payload.
  2. Confirm the endpoint URL is https://api.linear.app/graphql.
  3. Check for and surface any GraphQL `errors` array in the response before/instead of unmarshalling.
  4. Update IssuesResponse struct fields if the Linear schema changed.

Example fix

// before
if err := json.Unmarshal(data, &issuesResp); err != nil {
    return nil, fmt.Errorf("failed to parse description search response: %w", err)
}
// after
if err := json.Unmarshal(data, &issuesResp); err != nil {
    return nil, fmt.Errorf("failed to parse description search response: %w (body: %.200s)", err, data)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// verify endpoint before search
if client.Endpoint != "https://api.linear.app/graphql" {
    return errors.New("unexpected Linear endpoint")
}

Type guard

func validIssuesPayload(data []byte) bool {
    var probe struct {
        Issues *struct {
            Nodes []json.RawMessage `json:"nodes"`
        } `json:"issues"`
    }
    return json.Unmarshal(data, &probe) == nil && probe.Issues != nil
}

Try / catch

issue, err := client.FindIssueByDescriptionContains(ctx, desc)
if err != nil {
    var typeErr *json.UnmarshalTypeError
    if errors.As(err, &typeErr) {
        log.Printf("unexpected search response shape at %s; skipping dedupe", typeErr.Field)
        return nil // proceed without dedupe or use cache
    }
    return err
}

Prevention

When it happens

Trigger: Linear returns 200 with a body that doesn't fit IssuesResponse — GraphQL error partial payloads, schema changes to the issues connection, proxy/HTML error bodies with a 200 status, or truncated responses.

Common situations: Hitting the wrong endpoint (REST instead of /graphql), corporate proxies returning HTML interstitials, Linear schema updates renaming `issues` or `nodes`, mid-request connection drops producing truncated JSON.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at internal/linear/client.go:786

			"contains": text,
		},
	}

	req := &GraphQLRequest{
		Query: query,
		Variables: map[string]interface{}{
			"filter": filter,
		},
	}

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

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

	if len(issuesResp.Issues.Nodes) > 0 {
		return &issuesResp.Issues.Nodes[0], nil
	}
	return nil, nil
}

// issueCreateMutation is the GraphQL mutation for creating a Linear issue.
const issueCreateMutation = `
	mutation CreateIssue($input: IssueCreateInput!) {
		issueCreate(input: $input) {
			success
			issue {
				id
				identifier
				title
				description

View on GitHub (pinned to 71377f2769)