gastownhall/beads · error
no known transition path from %q to %q for %s: %w
Error message
no known transition path from %q to %q for %s: %w
What it means
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>'.
Source
Thrown at internal/ado/statetransition.go:125
}
// 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, nil
}
View on GitHub (pinned to 71377f2769)
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.
Example fix
// before: hardcoded paths only for built-in processes
path := resolveTransitionPath(workItemType, currentState, targetState)
// after: validate target state exists before transitioning
states, err := c.GetWorkItemStates(ctx, project, workItemType)
if err == nil && !containsState(states, targetState) {
return nil, fmt.Errorf("state %q does not exist for %s in this process; valid: %v", targetState, workItemType, states)
}
path := resolveTransitionPath(workItemType, currentState, targetState) Defensive patterns
Strategy: validation
Validate before calling
states, err := client.GetWorkItemStates(ctx, project, workItemType)
if err != nil { return err }
valid := map[string]bool{}
for _, s := range states { valid[s.Name] = true }
if !valid[targetState] {
return fmt.Errorf("state %q not valid for %s; valid: %v", targetState, workItemType, states)
} Type guard
func hasTransitionPath(workItemType, from, to string) bool {
return len(resolveTransitionPath(workItemType, from, to)) > 0
} Try / catch
wi, err := client.TransitionWorkItem(ctx, id, target)
if err != nil && strings.Contains(err.Error(), "no known transition path") {
// Fall back: transition to the default intermediate first, then retry
if _, err2 := client.TransitionWorkItem(ctx, id, "Active"); err2 == nil {
_, err = client.TransitionWorkItem(ctx, id, target)
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- invalid ADO configuration: %w
- transitioning to %q: %w
- transitioning to intermediate state %q: %w
- Azure DevOps PAT not configured (set ado.pat or AZURE_DEVOPS
- multiple .doltcfg directories detected
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/31bca09c9c4ef4b2.
Report an issue: GitHub.