Error Handling, Retry & Timeout
When a transition throws an error, the framework rolls back all changes and gives you three ways to recover: auto-retry with backoff, transition to a custom error state, or manual retry via the UI.
Retry Modes
Auto-Retry
Automatically re-run a failed transition with exponential backoff:
@Transition({ from: 'fetching', to: 'done', retry: 3 })
async fetchData() {
await this.httpClient.call({ url: 'https://api.example.com/data' });
}If fetchData throws, the framework retries up to 3 times with exponential delays (1s, 2s, 4s). The workflow stays at the fetching place during retries.
Custom Error Place
Route to a dedicated error state when a transition fails:
@Transition({ from: 'processing', to: 'done', retry: { place: 'error_processing' } })
async processData() {
await this.processor.call({ data: this.rawData });
}
@Transition({ from: 'error_processing', to: 'done', wait: true })
async handleProcessingError() {
// Recovery logic — user clicks a "Recover" button to trigger this
await this.repository.save(MessageDocument, {
role: 'assistant',
content: 'Processing failed. Retrying with fallback strategy.',
});
}The workflow transitions to error_processing where you define recovery logic via wait: true transitions (buttons in the UI).
Manual Retry (Default)
When no retry config is specified, the workflow stays at the current place and shows a “Retry” button:
@Transition({ from: 'sending', to: 'sent' })
async sendEmail() {
await this.email.call({ to: 'user@example.com', body: this.content });
}If it fails, the user sees the error message and can retry manually. No auto-retry, no error place — just pause and let the user decide.
Hybrid: Auto-Retry + Error Place
Combine auto-retry with a fallback error state:
@Transition({
from: 'deploying',
to: 'deployed',
retry: { attempts: 2, place: 'deploy_failed' },
})
async deploy() {
await this.deployer.call({ target: 'production' });
}
@Transition({ from: 'deploy_failed', to: 'deployed', wait: true })
async retryDeploy() {
// Manual recovery after auto-retries exhausted
}Retries twice automatically. If both fail, transitions to deploy_failed for manual intervention.
Retry Configuration
retry: number // Shorthand: number of auto-retry attempts
retry: {
attempts?: number, // Auto-retry count (-1 = manual only, 0 = go to error place immediately)
delay?: number, // Base delay in ms (default: 1000)
backoff?: 'fixed' | 'exponential', // Backoff strategy (default: 'exponential')
maxDelay?: number, // Backoff cap in ms (default: 30000)
place?: string, // Custom error place when retries exhausted
}Backoff calculation (exponential): delay * 2^(attempt - 1), capped at maxDelay.
| Attempt | Delay (default config) |
|---|---|
| 1 | 1,000ms |
| 2 | 2,000ms |
| 3 | 4,000ms |
| 4 | 8,000ms |
| 5 | 16,000ms |
| 6+ | 30,000ms (capped) |
Timeout
Kill a transition that takes too long:
@Transition({ from: 'analyzing', to: 'analyzed', timeout: 5000 })
async analyzeData() {
await this.analyzer.call({ dataset: this.data });
}If analyzeData takes longer than 5 seconds, it’s interrupted with Error: Transition 'analyzeData' timed out after 5000ms. The error flows through the normal retry logic — so you can combine timeout with retry:
@Transition({
from: 'analyzing',
to: 'analyzed',
timeout: 5000,
retry: 2,
})
async analyzeData() {
await this.analyzer.call({ dataset: this.data });
}Times out after 5s, retries up to 2 times, then falls to manual retry.
What Gets Rolled Back
When a transition fails, the framework rolls back:
- Documents created during the transition (restored from snapshot)
- Database changes within the transition’s transaction
- Workflow state stays at the pre-transition place
What is not rolled back:
- An
ErrorDocumentis saved after rollback as an audit trail - The error message is stored in workflow metadata
- Workflow instance variables (
this.someField) persist across retries — useful for attempt counters
ErrorDocument
Every failed transition creates an ErrorDocument with the error message:
// Automatically created by the framework:
{
className: 'ErrorDocument',
content: { error: 'Connection refused: api.example.com' }
}Multiple ErrorDocuments accumulate if retries fail repeatedly — giving a full audit trail of each attempt.
Registry References
- error-retry-example-workflow — Demonstrates all five retry modes: auto-retry, manual retry, custom error place, timeout, and hybrid