gastownhall/beads · error

failed to parse projects value

Error message

failed to parse projects value: %w

What it means

This error means the list envelope decoded but the nested 'value' field could not be unmarshalled into []Project. The top-level JSON looked right, but the items inside 'value' do not match the Project struct (unexpected field types or 'value' not an array).

Solutions

  1. Inspect envelope.Value raw JSON and compare against the library's Project struct field types
  2. Pin/check the api-version the library sends (addAPIVersion) against ADO's current projects schema
  3. Upgrade the ado package to a version matching the current ADO response schema
  4. If using a mock/proxy for tests, fix the fixture so 'value' is an array of Project-shaped objects

Example fix

// before (fixture)
{"value": {"id": 1, "name": "p"}}
// after
{"count": 1, "value": [{"id": "abcd-...", "name": "p"}]}
Defensive patterns

Strategy: type-guard

Type guard

// validate a projects payload shape before trusting it
type listEnvelope struct {
  Count int             `json:"count"`
  Value json.RawMessage `json:"value"`
}
func projectsPayloadOK(body []byte) bool {
  var env listEnvelope
  if json.Unmarshal(body, &env) != nil || env.Value == nil {
    return false
  }
  var arr []map[string]any
  return json.Unmarshal(env.Value, &arr) == nil
}

Try / catch

projects, err := client.ListProjects(ctx)
if err != nil {
  if strings.Contains(err.Error(), "failed to parse projects value") {
    return nil, fmt.Errorf("ADO projects schema drift — check api-version/library version: %w", err)
  }
  return nil, err
}

Prevention

When it happens

Trigger: Calling ListProjects when ADO's projects payload has items whose fields don't match the Project struct's types (e.g. an 'id' that isn't a string, or 'value' being an object/null instead of an array) — typically after an API version change or schema drift.

Common situations: ADO rolling out an API response change; using an api-version whose schema differs from what the library expects; mocking proxies (e.g. recorded fixtures) returning differently-typed fields.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/ado/client.go:582

}

// ListProjects returns all team projects in the organization.
// This is an org-level endpoint, not project-scoped.
func (c *Client) ListProjects(ctx context.Context) ([]Project, error) {
	urlStr := addAPIVersion(c.orgBase() + "/projects")
	respBody, err := c.doRequest(ctx, http.MethodGet, urlStr, "", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to list projects: %w", err)
	}

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

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

// GetWorkItemTypes returns the work item types available in the project.
func (c *Client) GetWorkItemTypes(ctx context.Context) ([]WorkItemType, error) {
	urlStr := addAPIVersion(c.apiBase() + "/wit/workitemtypes")
	respBody, err := c.doRequest(ctx, http.MethodGet, urlStr, "", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get work item types: %w", err)
	}

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

	var types []WorkItemType

View on GitHub (pinned to 71377f2769)