{"record":{"id":"d1bdeb340eb2a838","repo":"gastownhall/beads","slug":"transitioning-to-q-w","errorCode":null,"errorMessage":"transitioning to %q: %w","messagePattern":"transitioning to %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ado/statetransition.go","lineNumber":120,"sourceCode":"// direct state update; if that fails with a 400 error, it walks through the\n// known transition path for the work item type.\nfunc (c *Client) transitionWorkItem(ctx context.Context, workItemID int, workItemType, currentState, targetState string) (*WorkItem, error) {\n\tif currentState == targetState {\n\t\treturn nil, nil\n\t}\n\n\t// Try direct transition first.\n\tfields := map[string]interface{}{FieldState: targetState}\n\twi, err := c.UpdateWorkItem(ctx, workItemID, fields)\n\tif err == nil {\n\t\treturn wi, nil\n\t}\n\n\t// If direct transition failed with a 400 Bad Request, try walking\n\t// through intermediate states. Any other error is a real failure.\n\tvar apiErr *APIError\n\tif !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusBadRequest {\n\t\treturn nil, fmt.Errorf(\"transitioning to %q: %w\", targetState, err)\n\t}\n\n\tpath := resolveTransitionPath(workItemType, currentState, targetState)\n\tif len(path) == 0 {\n\t\treturn nil, fmt.Errorf(\"no known transition path from %q to %q for %s: %w\",\n\t\t\tcurrentState, targetState, workItemType, err)\n\t}\n\n\t// Walk through intermediate states.\n\tvar lastWI *WorkItem\n\tfor _, intermediate := range path {\n\t\tfields := map[string]interface{}{FieldState: intermediate}\n\t\tlastWI, err = c.UpdateWorkItem(ctx, workItemID, fields)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"transitioning to intermediate state %q: %w\", intermediate, err)\n\t\t}\n\t}\n\treturn lastWI, nil","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/ado/statetransition.go#L102-L138","documentation":"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>\"'.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: retrying blindly on any failure\nwi, err := c.transitionWorkItem(ctx, id, \"Closed\")\n// after: only retry transient failures\nwi, err := c.transitionWorkItem(ctx, id, \"Closed\")\nif err != nil {\n    var apiErr *APIError\n    if errors.As(err, &apiErr) && apiErr.StatusCode >= 500 {\n        time.Sleep(2 * time.Second)\n        wi, err = c.transitionWorkItem(ctx, id, \"Closed\")\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Confirm the work item exists and is writable before transitioning\nwi, resp, err := client.GetWorkItem(ctx, id, nil)\nif err != nil || resp == nil || resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"work item %d not accessible: %v\", id, err)\n}\n_ = wi","typeGuard":"func isRetryableTransitionErr(err error) bool {\n    var apiErr *APIError\n    if !errors.As(err, &apiErr) { return false }\n    return apiErr.StatusCode >= 500 || apiErr.StatusCode == http.StatusTooManyRequests\n}","tryCatchPattern":"wi, err := client.TransitionWorkItem(ctx, id, \"Closed\")\nif err != nil {\n    var apiErr *APIError\n    if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusUnauthorized {\n        return fmt.Errorf(\"check ado.pat permissions: %w\", err)\n    }\n    if isRetryableTransitionErr(err) {\n        return retryWithBackoff(func() error {\n            _, err = client.TransitionWorkItem(ctx, id, \"Closed\")\n            return err\n        })\n    }\n    return err\n}","preventionTips":["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."],"tags":["azure-devops","state-transition","http-error"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}