gastownhall/beads · error
transitioning to %q: %w
Error message
transitioning to %q: %w
What it means
transitionWorkItem attempted to set a work item's state directly via UpdateWorkItem and the call failed with an error that is not an APIError 400 (Bad Request). Since only 400s are eligible for multi-hop transition walking, any other failure (auth, network, 404, 500) is wrapped as 'transitioning to "<targetState>"'.
Source
Thrown at internal/ado/statetransition.go:120
// direct state update; if that fails with a 400 error, it walks through the
// known transition path for the work item type.
func (c *Client) transitionWorkItem(ctx context.Context, workItemID int, workItemType, currentState, targetState string) (*WorkItem, error) {
if currentState == targetState {
return nil, nil
}
// Try direct transition first.
fields := map[string]interface{}{FieldState: targetState}
wi, err := c.UpdateWorkItem(ctx, workItemID, fields)
if err == nil {
return wi, nil
}
// If direct transition failed with a 400 Bad Request, try walking
// through intermediate states. Any other error is a real failure.
var apiErr *APIError
if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusBadRequest {
return nil, fmt.Errorf("transitioning to %q: %w", targetState, err)
}
path := resolveTransitionPath(workItemType, currentState, targetState)
if len(path) == 0 {
return nil, fmt.Errorf("no known transition path from %q to %q for %s: %w",
currentState, targetState, workItemType, err)
}
// Walk through intermediate states.
var lastWI *WorkItem
for _, intermediate := range path {
fields := map[string]interface{}{FieldState: intermediate}
lastWI, err = c.UpdateWorkItem(ctx, workItemID, fields)
if err != nil {
return nil, fmt.Errorf("transitioning to intermediate state %q: %w", intermediate, err)
}
}
return lastWI, nilView on GitHub (pinned to 71377f2769)
Solutions
- Unwrap the inner error and check the HTTP status: fix PAT permissions for 401/403.
- Confirm the work item still exists (404) — it may have been deleted in ADO.
- For 409 conflicts, refresh the work item and retry the transition.
- Retry after transient 5xx/network failures; the transition is a single idempotent field update.
Example fix
// before: retrying blindly on any failure
wi, err := c.transitionWorkItem(ctx, id, "Closed")
// after: only retry transient failures
wi, err := c.transitionWorkItem(ctx, id, "Closed")
if err != nil {
var apiErr *APIError
if errors.As(err, &apiErr) && apiErr.StatusCode >= 500 {
time.Sleep(2 * time.Second)
wi, err = c.transitionWorkItem(ctx, id, "Closed")
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the work item exists and is writable before transitioning
wi, resp, err := client.GetWorkItem(ctx, id, nil)
if err != nil || resp == nil || resp.StatusCode != http.StatusOK {
return fmt.Errorf("work item %d not accessible: %v", id, err)
}
_ = wi Type guard
func isRetryableTransitionErr(err error) bool {
var apiErr *APIError
if !errors.As(err, &apiErr) { return false }
return apiErr.StatusCode >= 500 || apiErr.StatusCode == http.StatusTooManyRequests
} Try / catch
wi, err := client.TransitionWorkItem(ctx, id, "Closed")
if err != nil {
var apiErr *APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("check ado.pat permissions: %w", err)
}
if isRetryableTransitionErr(err) {
return retryWithBackoff(func() error {
_, err = client.TransitionWorkItem(ctx, id, "Closed")
return err
})
}
return err
} Prevention
- Keep the PAT valid with Work Items Write scope.
- Check the work item still exists before transitioning (it may have been deleted).
- Refresh work items before updates to avoid revision conflicts.
- Only multi-hop walk applies to 400s; handle 401/404/5xx explicitly in callers.
When it happens
Trigger: Calling a state transition when UpdateWorkItem fails with 401/403 (permissions or revoked PAT), 404 (work item ID doesn't exist), 500/503 (ADO outage), network timeout, or a non-400 APIError like 409 conflict.
Common situations: PAT lacks write access; the work item was deleted between lookup and update; ADO is temporarily unavailable; stale work item revision causing a 409 conflict.
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
- transitioning to intermediate state %q: %w
- remove relation %d: %w
- add link %s to %d: %w
- work item %d: %w
- no known transition path from %q to %q for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d1bdeb340eb2a838.
Report an issue: GitHub.