ruvnet/ruflo · error
Event must have a valid type string
Error message
Event must have a valid type string
What it means
As the second guard in RvfEventLog.append(), the event's type field must be a non-empty string before the event is versioned and persisted. The type drives dispatch during replay, so a missing, blank, or non-string type is rejected up front. This typically means the event was constructed without a discriminator or with a renamed field.
Source
Thrown at v3/@claude-flow/shared/src/events/rvf-event-log.ts:166
this.snapshots.clear();
this.initialized = false;
this.emit('shutdown');
}
// ===========================================================================
// Write Operations
// ===========================================================================
/** Append a domain event to the log. */
async append(event: DomainEvent): Promise<void> {
this.ensureInitialized();
if (!event.aggregateId || typeof event.aggregateId !== 'string') {
throw new Error('Event must have a valid aggregateId string');
}
if (!event.type || typeof event.type !== 'string') {
throw new Error('Event must have a valid type string');
}
// Assign next version for aggregate
const currentVersion = this.aggregateVersions.get(event.aggregateId) ?? 0;
const nextVersion = currentVersion + 1;
event.version = nextVersion;
// Persist to disk first (crash-safe ordering)
this.appendRecord(this.config.logPath, event);
// Update in-memory state
this.indexEvent(event);
this.emit('event:appended', event);
if (nextVersion % this.config.snapshotThreshold === 0) {
this.emit('snapshot:recommended', {
aggregateId: event.aggregateId,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Always set type from a shared constant or enum at the append site
- Check the event shape with a type guard before append() (covers aggregateId and type together)
- After renames, grep for old type constant names to catch stale producers
Example fix
// before
await log.append({ aggregateId: id } as DomainEvent); // throws: invalid type
// after
await log.append({ aggregateId: id, type: 'order.created' } as DomainEvent); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof event.type !== 'string' || event.type.length === 0) {
throw new TypeError('DomainEvent.type must be a non-empty string');
}
await log.append(event); Type guard
function hasEventType(e: unknown): e is DomainEvent {
return !!e && typeof e === 'object'
&& typeof (e as any).type === 'string' && (e as any).type.length > 0
&& typeof (e as any).aggregateId === 'string' && (e as any).aggregateId.length > 0;
} Try / catch
try {
await log.append(event);
} catch (e) {
if (e instanceof Error && e.message.includes('valid type string')) {
// producer sent an event without a type discriminator: fix upstream
} else throw e;
} Prevention
- Define event types as a const/enum and require them in the event factory
- Guard deserialized events with hasEventType() before append
- After renaming type constants, sweep for stale producers
When it happens
Trigger: append({ aggregateId, payload }) with no type; type: '' or type: undefined; type passed as a symbol/number; spread construction that overwrites or drops type.
Common situations: Factory helpers defaulting type only for some event kinds; refactors renaming event type constants; deserialization dropping the field; payloads mistakenly passed as the whole event.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Event must have a valid aggregateId string
- [RvfEventLog] Invalid file header in ${filePath}
- RvfEventLog not initialized. Call initialize() first.
- localCompute: no adapter for graphId=${input.graphId}
- Invalid completion type
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/0aa5e95b3ac600ca.
Report an issue: GitHub.