{"record":{"id":"31bca09c9c4ef4b2","repo":"gastownhall/beads","slug":"no-known-transition-path-from-q-to-q-for-s-w","errorCode":null,"errorMessage":"no known transition path from %q to %q for %s: %w","messagePattern":"no known transition path from %q to %q for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ado/statetransition.go","lineNumber":125,"sourceCode":"\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\n}\n","sourceCodeStart":107,"sourceCodeEnd":140,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/ado/statetransition.go#L107-L140","documentation":"After a direct state update failed with HTTP 400, transitionWorkItem consults resolveTransitionPath for a known multi-hop path between the current and target state for that work item type. If no path is registered (or current==target makes one impossible), the original 400 is re-wrapped as 'no known transition path from \"<from>\" to \"<to>\" for <type>'.","triggerScenarios":"Setting a state whose direct transition is rejected with 400 while resolveTransitionPath(workItemType, currentState, targetState) returns an empty slice — i.e. the (currentState, targetState, workItemType) tuple isn't in the hardcoded transition table.","commonSituations":"Using a state name not present in your ADO process template (typo, renamed column); a custom/inherited process whose state graph differs from the built-in table; transitioning between states on an unexpected work item type (e.g. Bug vs User Story rules).","solutions":["Check the target state name for typos and confirm it exists in the ADO process (GetWorkItemStates can list valid states).","Transition manually to a known intermediate state (e.g. Active) or update the transition table in resolveTransitionPath to cover your process.","If using a custom process, map your states to the built-in ones or configure the state graph.","Reproduce the underlying 400 (wrapped as %w) to confirm the direct transition was genuinely invalid."],"exampleFix":"// before: hardcoded paths only for built-in processes\npath := resolveTransitionPath(workItemType, currentState, targetState)\n// after: validate target state exists before transitioning\nstates, err := c.GetWorkItemStates(ctx, project, workItemType)\nif err == nil && !containsState(states, targetState) {\n    return nil, fmt.Errorf(\"state %q does not exist for %s in this process; valid: %v\", targetState, workItemType, states)\n}\npath := resolveTransitionPath(workItemType, currentState, targetState)","handlingStrategy":"validation","validationCode":"states, err := client.GetWorkItemStates(ctx, project, workItemType)\nif err != nil { return err }\nvalid := map[string]bool{}\nfor _, s := range states { valid[s.Name] = true }\nif !valid[targetState] {\n    return fmt.Errorf(\"state %q not valid for %s; valid: %v\", targetState, workItemType, states)\n}","typeGuard":"func hasTransitionPath(workItemType, from, to string) bool {\n    return len(resolveTransitionPath(workItemType, from, to)) > 0\n}","tryCatchPattern":"wi, err := client.TransitionWorkItem(ctx, id, target)\nif err != nil && strings.Contains(err.Error(), \"no known transition path\") {\n    // Fall back: transition to the default intermediate first, then retry\n    if _, err2 := client.TransitionWorkItem(ctx, id, \"Active\"); err2 == nil {\n        _, err = client.TransitionWorkItem(ctx, id, target)\n    }\n}","preventionTips":["Validate state names against GetWorkItemStates before transitioning.","Keep resolveTransitionPath in sync with your ADO process (especially for custom/inherited processes).","Avoid typos by deriving state names from constants or config, not free-form strings.","Check the wrapped 400 error to confirm the direct transition failure reason."],"tags":["azure-devops","state-transition","configuration","process-template"],"backgroundTag":"invalid-state-transition","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}