gastownhall/beads · error

failed to fetch work items: %w

Error message

failed to fetch work items: %w

What it means

FetchWorkItems wraps any error from the batched GET /wit/workitems?ids=...&$expand=All call (which itself goes through doRequest) with "failed to fetch work items". It is a transport/execution wrapper: the cause is either a doRequest failure (rate limits, retries exhausted, auth) or an HTTP error returned by the ADO work-items endpoint. It does NOT cover JSON parsing — that produces "failed to parse work items response" instead.

Source

Thrown at internal/ado/client.go:353

	}

	var all []WorkItem
	for start := 0; start < len(ids); start += MaxBatchSize {
		end := start + MaxBatchSize
		if end > len(ids) {
			end = len(ids)
		}
		chunk := ids[start:end]

		parts := make([]string, len(chunk))
		for i, id := range chunk {
			parts[i] = strconv.Itoa(id)
		}

		urlStr := addAPIVersion(c.apiBase() + "/wit/workitems?ids=" + strings.Join(parts, ",") + "&$expand=All")
		respBody, err := c.doRequest(ctx, http.MethodGet, urlStr, "", nil)
		if err != nil {
			return nil, fmt.Errorf("failed to fetch work items: %w", err)
		}

		var envelope listResponse
		if err := json.Unmarshal(respBody, &envelope); err != nil {
			return nil, fmt.Errorf("failed to parse work items response: %w", err)
		}

		var items []WorkItem
		if err := json.Unmarshal(envelope.Value, &items); err != nil {
			return nil, fmt.Errorf("failed to parse work items value: %w", err)
		}
		all = append(all, items...)
	}

	return all, nil
}

// buildPullWIQL constructs a safe WIQL query from validated filter fields.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (%v of err) for the underlying status/body — APIError carries StatusCode and Body.
  2. Verify the PAT has 'Work Items (Read)' scope and access to the project/area paths of the returned IDs.
  3. Split large ID lists into smaller batches (URL length / API limits) and retry.
  4. Remove or re-resolve stale IDs (deleted work items) before calling; re-run the WIQL query.
  5. Confirm the client's organization/project URL is correct (wrong org yields 404s from the workitems endpoint).

Example fix

// before
respBody, err := c.doRequest(ctx, http.MethodGet, urlStr, "", nil)
if err != nil { return nil, fmt.Errorf("failed to fetch work items: %w", err) }
// after
caller side:
items, err := c.FetchWorkItems(ctx, ids)
if err != nil {
    var apiErr *ado.APIError
    if errors.As(err, &apiErr) { log.Printf("ADO %d: %s", apiErr.StatusCode, apiErr.Body) }
    return nil, err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate IDs exist / are readable before batch fetch
for _, id := range ids {
    if id <= 0 { return fmt.Errorf("invalid work item id %d", id) }
}
if len(ids) > 200 { ids = ids[:200] } // keep URL within limits

Try / catch

items, err := client.FetchWorkItems(ctx, ids)
if err != nil {
    var apiErr *ado.APIError
    if errors.As(err, &apiErr) {
        switch apiErr.StatusCode {
        case 401, 403: // fix PAT / permissions
        case 400:    // trim id list, re-run WIQL
        default:     // transient: retry
        }
    }
    return err
}

Prevention

When it happens

Trigger: Calling FetchWorkItems (including via fetchWorkItemsByWIQL, which resolves WIQL results into IDs then calls FetchWorkItems) when the batched ids GET fails: retries exhausted on 429/5xx, invalid/nonexistent work-item IDs, insufficient PAT scope, revoked PAT, or network failure.

Common situations: WIQL query returns IDs the caller's PAT cannot read (area-path permissions); deleted/recycled work-item IDs in the id list; huge id batches exceeding URL limits; org URL misconfigured in the client base URL.

Related errors


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