gastownhall/beads · error

failed to remove work item link: %w

Error message

failed to remove work item link: %w

What it means

This error wraps any failure from the HTTP PATCH that deletes a relation (JSON-Patch 'remove /relations/{index}') from a work item in RemoveWorkItemLink. Like its sibling add-link error, it wraps the original doRequest error with %w so status codes and ADO messages remain inspectable. It marks the failure at the 'remove link' call boundary.

Source

Thrown at internal/ado/client.go:561

	_, err := c.doRequest(ctx, http.MethodPatch, urlStr, "application/json-patch+json", ops)
	if err != nil {
		return fmt.Errorf("failed to add work item link: %w", err)
	}
	return nil
}

// RemoveWorkItemLink removes a relation link by index from the given work item.
func (c *Client) RemoveWorkItemLink(ctx context.Context, sourceID, relationIndex int) error {
	ops := []PatchOperation{
		{
			Op:   "remove",
			Path: fmt.Sprintf("/relations/%d", relationIndex),
		},
	}
	urlStr := addAPIVersion(fmt.Sprintf("%s/wit/workitems/%d", c.apiBase(), sourceID))
	_, err := c.doRequest(ctx, http.MethodPatch, urlStr, "application/json-patch+json", ops)
	if err != nil {
		return fmt.Errorf("failed to remove work item link: %w", err)
	}
	return nil
}

// 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)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-fetch the work item's current relations and recompute relationIndex before retrying (indices shift on concurrent changes)
  2. Check the wrapped cause (errors.As) for ADO's status code — 404 usually means stale index or wrong ID
  3. Verify the PAT has Work Items write scope and has not expired
  4. Confirm sourceID exists in the configured organization/project
  5. Retry transient network/5xx failures; ADO occasionally throttles writes (429)

Example fix

// before
item, _ := client.GetWorkItem(ctx, id)
idx := findRel(item, relURL)
client.RemoveWorkItemLink(ctx, id, idx) // idx may be stale later
// after
item, _ := client.GetWorkItem(ctx, id)
idx := findRel(item, relURL)
if idx < 0 {
  return nil // nothing to remove
}
if err := client.RemoveWorkItemLink(ctx, id, idx); err != nil {
  return fmt.Errorf("remove link from %d (idx %d): %w", id, idx, err)
}
Defensive patterns

Strategy: validation

Validate before calling

// validate the index against freshly fetched relations immediately before removal
wi, err := client.GetWorkItem(ctx, sourceID)
if err != nil {
  return err
}
if relationIndex < 0 || relationIndex >= len(wi.Relations) {
  return fmt.Errorf("relation index %d out of range (have %d)", relationIndex, len(wi.Relations))
}

Type guard

func validRelationIndex(n, total int) bool { return n >= 0 && n < total }

Try / catch

if err := client.RemoveWorkItemLink(ctx, sourceID, idx); err != nil {
  if strings.Contains(err.Error(), "404") {
    // stale index: refetch relations and recompute
    return retryWithFreshIndex(ctx, sourceID, relURL)
  }
  return err
}

Prevention

When it happens

Trigger: Calling Client.RemoveWorkItemLink(ctx, sourceID, relationIndex) when doRequest fails: 404 because sourceID or the relation index no longer exists (relations shift when other links are removed), 401 invalid PAT, network error, or 400 for a negative/out-of-range index.

Common situations: Removing a link by a stale index captured before another process modified relations; concurrent edits to the work item causing index drift; expired PAT after long-running jobs; typo in the work item ID.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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