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

  1. Check the target state name for typos and confirm it exists in the ADO process (GetWorkItemStates can list valid states).
  2. Transition manually to a known intermediate state (e.g. Active) or update the transition table in resolveTransitionPath to cover your process.
  3. If using a custom process, map your states to the built-in ones or configure the state graph.
  4. 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

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


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