ruvnet/ruflo · error
Invalid state transition from '${from}' to '${to}'. Allowed
Error message
Invalid state transition from '${from}' to '${to}'. Allowed transitions from '${from}': ${allowed?.join(', ') ?? 'none'} What it means
assertValidStateTransition() checks that to is listed in allowedTransitions[from]; it throws when from has no entry at all or to is not permitted, printing the allowed set or none. It encodes a state-machine table in tests, so a failure means the code under test attempted (or was claimed to attempt) an illegal transition.
Source
Thrown at v3/@claude-flow/testing/src/helpers/assertion-helpers.ts:487
/**
* Assert state transition is valid
*
* @example
* assertValidStateTransition(
* 'pending',
* 'running',
* { pending: ['running', 'cancelled'], running: ['completed', 'failed'] }
* );
*/
export function assertValidStateTransition<T extends string>(
from: T,
to: T,
allowedTransitions: Record<T, T[]>
): void {
const allowed = allowedTransitions[from];
if (!allowed || !allowed.includes(to)) {
throw new Error(
`Invalid state transition from '${from}' to '${to}'. ` +
`Allowed transitions from '${from}': ${allowed?.join(', ') ?? 'none'}`
);
}
}
/**
* Assert that a retry policy was followed
*
* @example
* assertRetryPattern(mockFn, { attempts: 3, backoffPattern: 'exponential' });
*/
export function assertRetryPattern(
mock: Mock,
options: RetryPatternOptions
): void {
const calls = mock.mock.calls;
View on GitHub (pinned to fa13ee4ad6)
Solutions
- Compare the thrown allowed list against the attempted transition; either the code took a bad path or the table is stale
- Add the missing state key or transition pair to allowedTransitions if the transition is now legal
- Derive the test table from the same constant the production state machine uses
Example fix
// before
assertValidStateTransition('pending', 'completed', TRANSITIONS); // throws
// after
assertValidStateTransition('pending', 'running', TRANSITIONS);
// or, if skipping is now legal: TRANSITIONS.pending.push('completed') Defensive patterns
Strategy: type-guard
Validate before calling
function isAllowedTransition<T extends string>(from: T, to: T, table: Record<T, T[]>): boolean {
return table[from]?.includes(to) ?? false;
}
if (!isAllowedTransition(from, to, TRANSITIONS)) {
throw new Error(`Refusing illegal transition ${from} -> ${to}`);
} Type guard
function isAllowedTransition<T extends string>(from: T, to: T, table: Record<T, T[]>): to is T {
return table[from]?.includes(to) ?? false;
} Prevention
- Single-source the transition table shared by runtime and tests
- Type the table as Record<T, T[]> so a missing state key is a compile error
- Add a test that enumerates every declared transition, not just the happy path
When it happens
Trigger: Asserting pending to completed when the table only allows pending to running or cancelled; a from value that is not a key in the table (reads as no transitions); enum string mismatches such as Running vs running.
Common situations: State machine extended with new states but the test table not updated; transition maps copied and drifting from the production machine; renames or casing changes after refactors.
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 domain object: ${result.errors?.join(', ')}
- Mock was called with unexpected arguments: ${JSON.stringify(
- Expected to find ${JSON.stringify(expected)} after index ${l
- Item at index ${i} failed predicate: ${JSON.stringify(items[
- Item at index ${i} passed predicate but should not have: ${J
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/13542faab988702d.
Report an issue: GitHub.