| name | debugging-workflows |
| description | Test, debug, and troubleshoot Flowcraft workflow executions. Covers test utilities, time-travel debugging, event analysis, and common error patterns. Use when debugging workflows, writing tests, analyzing execution events, replaying workflow runs, or troubleshooting workflow failures. |
Debugging Workflows
Flowcraft provides comprehensive debugging tools including event capturing, execution tracing, and time-travel replay.
Quick start
Test with trace output
import { createFlow, FlowRuntime } from 'flowcraft'
import { runWithTrace } from 'flowcraft/testing'
const flow = createFlow('my-flow')
.node('step1', async () => ({ output: 'data' }))
.node('step2', async () => ({ output: 'more' }))
.edge('step1', 'step2')
const runtime = new FlowRuntime()
const result = await runWithTrace(flow, runtime, { initial: 'data' })
Capture and assert events
import { InMemoryEventLogger } from 'flowcraft/testing'
const eventLogger = new InMemoryEventLogger()
const runtime = new FlowRuntime({ eventBus: eventLogger })
const result = await runtime.run(blueprint, {})
const nodeEvents = eventLogger.getEvents('node:finish')
expect(nodeEvents).toHaveLength(3)
const errors = eventLogger.getEvents('node:error')
expect(errors).toHaveLength(0)
Testing patterns
Event-based assertions
import { InMemoryEventLogger } from 'flowcraft/testing'
const eventLogger = new InMemoryEventLogger()
const runtime = new FlowRuntime({ eventBus: eventLogger })
await runtime.run(blueprint, { userId: '123' })
const order = eventLogger.getEvents('node:finish').map((e) => e.nodeId)
expect(order).toEqual(['fetch', 'transform', 'store'])
const contextChanges = eventLogger.getEvents('context:change')
expect(contextChanges.some((e) => e.key === 'result')).toBe(true)
Test error handling
const failingNode = vi
.fn()
.mockRejectedValueOnce(new Error('temp'))
.mockRejectedValueOnce(new Error('temp'))
.mockResolvedValue({ output: 'success' })
const flow = createFlow('retry-test').node('flaky', failingNode, { config: { maxRetries: 3 } })
const eventLogger = new InMemoryEventLogger()
const runtime = new FlowRuntime({ eventBus: eventLogger })
await runtime.run(flow.toBlueprint(), {})
expect(failingNode).toHaveBeenCalledTimes(3)
expect(eventLogger.getEvents('node:retry')).toHaveLength(2)
Time-travel debugging
Replay recorded events to reconstruct workflow state without re-executing nodes:
import { PersistentEventBusAdapter, InMemoryEventStore } from 'flowcraft'
const eventStore = new InMemoryEventStore()
const eventBus = new PersistentEventBusAdapter(eventStore)
const runtime = new FlowRuntime({ eventBus })
const result = await runtime.run(blueprint, initialContext)
const events = await eventStore.retrieve(result.context._executionId)
const replayResult = await runtime.replay(blueprint, events)
Partial replay from a specific node
For more granular debugging, replay from a specific point with modified inputs:
const replayResult = await runtime.replayFrom(blueprint, events, 'processNode', {
inputOverrides: { correctedInput: '...' },
functionRegistry: flow.getFunctionRegistry(),
})
Rollback execution
Undo context mutations after a target node to revert execution state:
const rolledBack = await runtime.rollbackExecution(blueprint, executionId, events, 'B')
Event types for replay
| Event | Purpose |
|---|
node:finish | Applies completed node outputs to context |
context:change | Applies context modifications |
node:error | Records errors in workflow state |
workflow:finish | Marks workflow completion |
Replay always produces completed status since it reconstructs state without re-execution.
Execution control
Patch context mid-execution
Modify workflow state without re-running nodes:
const patched = await runtime.patchContext(blueprint, executionId, events, [
{ key: 'status', value: 'corrected', op: 'set' },
])
Mark node completed
Skip a node or provide synthetic output without executing its logic:
const result = await runtime.markNodeCompleted(blueprint, executionId, 'skippedNode', {
synthetic: true,
})
Request pause
Programmatically pause a running execution at the next checkpoint:
runtime.requestPause(executionId)
Troubleshooting
See common-errors.md for detailed troubleshooting of:
- Stalled workflows (unresolved dependencies)
- Missing node implementations in registry
- Cycle detection errors
- Context type mismatches
- Edge condition evaluation failures
- Retry and timeout issues
Debugging checklist
- Check blueprint validity: Use
lintBlueprint(blueprint) to catch structural issues
- Visualize workflow: Use
generateMermaid(blueprint) to verify graph structure
- Analyze blueprint: Use
analyzeBlueprint(blueprint) for cycle/start/terminal info
- Capture events: Use
InMemoryEventLogger to trace execution flow
- Replay execution: Use
runtime.replay() to reconstruct state from events
- Partial replay: Use
runtime.replayFrom() to replay from a specific node with modified inputs
- Rollback state: Use
runtime.rollbackExecution() to undo nodes after a target point
- Patch context: Use
runtime.patchContext() to correct state without re-execution
- Check context: Inspect
result.context for expected state
Test coverage
Flowcraft maintains 85%+ line/function coverage thresholds (90%+ for critical files). Run pnpm test:coverage locally.