Skip to main content

Fault and Degraded-Mode Catalog

What each fault looks like while it is happening: the log event id you will see, the metric that moves, what the framework does without being asked, and what is left for you.

Every event id and metric name on this page is emitted by the shipped packages. Log event ids are stable identifiers — filter on the number, not on the message text, which may be reworded.

How to read an entry​

ColumnMeans
FaultThe underlying condition.
ObservableThe log event id, and the metric that moves. Log at Information or above to see the ones marked as warnings and errors.
Framework behaviourWhat happens without operator action.
Your actionWhat the framework cannot do for you.

A note on what "handled" means here. Most degraded modes in this catalog resolve into one of three outcomes — retry later, dead-letter, or refuse and stand down. None of them is "the message was lost", except where a row says so explicitly. Where a row says a message is discarded, that is a configuration you can close.

Index by fault class​

Fault classSections
Broker outageTransports and brokers, Outbox, Inbox
Database or store outageOutbox, Event store and snapshots, Sagas, Change data capture
Network partitionLeader election and fencing, Transports and brokers
Slow consumersOutbox, Inbox, Projections and materialized views
Disk, quota, or capacity exhaustionCapacity and quota exhaustion
Credentials expiringCredentials and key access
Clock skewClock skew
Leader lossLeader election and fencing

Transports and brokers​

FaultObservableFramework behaviourYour action
Broker unreachable; subscriber's receive loop faults21000 subscriber reconnecting, with the attempt number and the backoff it will waitThe subscriber reconnects on an unbounded backoff loop. It does not give up and does not stop the host.None while the broker is down. If the attempt counter climbs indefinitely, the broker is not coming back on its own.
A configured reconnect schedule returns a delay at or below the subscriber's floor21001 backoff floor appliedThe floor is used instead of the configured delay, so the reconnect loop cannot spin without pause.Fix the schedule. The floor is a guard, not a setting to rely on.
Broker rejecting publishes; the transport circuit breaker opens131220 from the outbox drain, naming the transport and the messageThe message is left for retry, not dead-lettered. An open breaker is a statement about the transport, not about the message.Nothing per-message. Restore the transport; the backlog drains.
Publish fails on the inbox dispatch path with an open breaker132218, naming the message typeSame: left for retry, not dead-lettered.As above.

Transport health is surfaced through health checks and the transport health metrics; see Health Checks.

Outbox​

Meters: Excalibur.Outbox.Store — excalibur.outbox.store.operations (counter, by operation type), excalibur.outbox.store.messages (counter of messages processed, not a queue depth) and excalibur.outbox.store.operation_duration (histogram, milliseconds) — and Excalibur.Dispatch.BackgroundServices (excalibur.background_service.messages_processed, excalibur.background_service.messages_failed, excalibur.background_service.processing_cycles, excalibur.background_service.processing_errors, excalibur.background_service.processing_duration).

FaultObservableFramework behaviourYour action
A drain cycle throws131302 background service error; excalibur.background_service.processing_errors risesThe cycle ends; the next one starts on the configured polling interval. Claimed messages stay claimed until their reservation lapses, then are re-claimed.Read the inner exception. Repeated errors with no processed messages is a store or transport problem, not an outbox one.
Shutdown could not drain in time131307 drain timeout exceeded, naming the configured timeoutThe host stops anyway. In-flight messages stay claimed and are re-claimed by the next process after their reservation lapses.Raise the drain timeout, or accept the redelivery. It is inside the at-least-once guarantee.
This instance is not the leader131308 skipped — not the leaderThe cycle is skipped entirely. This is normal on every non-leader instance.Nothing. If every instance logs this, no one holds leadership — see Leader election.
Delivery succeeded but the store write recording it failed131310, at ErrorThe delivery stands. The message is not dead-lettered — filing a delivered message as undeliverable is worse than the duplicate that may follow. The row is left claimed; a later drain completes the mark or redelivers.Nothing per-message. Handlers absorb the duplicate. Investigate the store write.
Dead-letter routing itself failed131309, at ErrorThe message stays claimed and is retried on a later cycle. The rest of the batch is unaffected.Check the dead-letter store.
A message failed and no dead-letter queue is registered131226 — the message text begins OUTBOX MESSAGE LOSTThe message is discarded permanently.Register an IDeadLetterQueue. 131224 warns about this at startup, before anything is lost.
A failure report was declined because the claim is no longer held134005 at InformationThe batch continues with the remaining messages. Another drain now owns that message.Nothing. Frequent occurrences mean reservations are shorter than your delivery latency.
A failure report wrote nothing — no such message, or already terminal134006 / 134010 at InformationThe batch continues.Nothing.
A failure report returned an outcome this build does not recognise134007 at WarningThe failure is treated as not recorded; the message stays claimed until its reservation lapses.Check for a store package at a different version from the drain.
The drain is running unfenced131228 at Information (no leader election registered) or 131227 at Warning (explicit AsSingleWriter() opt-out)The drain runs. Safe with exactly one draining process; a genuine multi-writer deployment has an open split-brain window.If more than one process drains this outbox, register a leader election on a store that supports fencing.
Backlog growing faster than the drain clears itexcalibur.background_service.processing_cycles keeps rising while excalibur.background_service.messages_processed stays flat or falls, and excalibur.outbox.store.operation_duration climbs. No shipped meter reports outbox queue depth — read it from the operational dashboard, or count unsent rows yourself.Nothing — this is a capacity condition, not an error.Raise batch size or parallelism, or add capacity to the transport. See Performance Tuning.

Inbox​

Meter: Excalibur.Inbox (excalibur.inbox.operations, excalibur.inbox.operation_duration).

FaultObservableFramework behaviourYour action
A redelivered message is already processed132003 duplicate detected, at the level your DuplicateBehavior selectsThe handler is not re-invoked. This is the inbox working.Nothing. A rising rate means the transport is redelivering more, which is worth understanding.
The retry drain finds an entry another processor holds132223The drain skips it. The holder will finish it.Nothing.
The drain's ownership term lapsed after the handler ran132224The handler's effect stands; the entry was reclaimed and will be retried by whoever now holds it. The handler may therefore run again.Handlers must be idempotent. If this is frequent, the lease is shorter than handler duration — raise it.
A message failed and no dead-letter queue is registered132222 — the message text begins INBOX MESSAGE LOSTDiscarded permanently.Register an IDeadLetterQueue. 132220 warns at startup.
A message was dead-lettered132217The entry is terminal; the transport is acknowledged.Replay from the dead-letter queue once the cause is fixed.
Shutdown could not drain in time132005The host stops. Unfinished entries are re-claimed after their lease lapses.As for the outbox.
Handler slower than the lease132224 rising, together with repeated 132003 for the same idsWork is re-admitted and re-run.Raise the lease, or make the handler faster. A duplicate window bounded by the lease is the documented guarantee, not a defect.

Event store and snapshots​

Meter: Excalibur.EventSourcing.EventStore and Excalibur.EventSourcing.SnapshotStore (excalibur.eventsourcing.eventstore.operations, …eventstore.duration, …eventstore.events_appended, …eventstore.events_loaded, …snapshotstore.operations, …snapshotstore.duration). Operations are tagged with operation.result, whose values include success, concurrency_conflict, failure and not_found — alert on the tag, not on a separate counter.

FaultObservableFramework behaviourYour action
Two writers append to one stream at the same version111104 concurrency conflict; operation.result=concurrency_conflict on the append counterThe append is rejected. Nothing is written.Reload the aggregate and retry the command. A sustained rate means two writers own one aggregate.
A snapshot is missing for an aggregate that should have one112003The aggregate is reconstructed from events. Slower, not wrong.Nothing, unless load times matter.
Auto-snapshotting is failingexcalibur.eventsourcing.auto_snapshot.failed rising against …auto_snapshot.evaluatedSnapshots are not created; replays lengthen over time.Investigate the snapshot store. Load latency degrades gradually, which is why the counter matters more than the symptom.
A catch-up subscription's poll fails114603The subscription continues; the position is not advanced past unprocessed events.Investigate the store. Replay on recovery is expected.
A subscription cannot deserialize a stored event114604That event is not delivered.Almost always a missing or renamed event type after a deployment. Register the type or add an upcaster.
An event was read for an erased subject114508 erased event skippedThe event is skipped rather than surfaced.Nothing. This is crypto-shredding working.
Cold-tier reads rising sharplyexcalibur.eventsourcing.archive.cold_reads, with …archive.errors and …archive.duration_secondsReads fall through to cold storage transparently.Expected after an archive run. Errors on this counter mean history is unreachable, which is a data-availability incident.

Projections and materialized views​

Meters: Excalibur.EventSourcing.Projections (excalibur.projection.error.count, tagged with projection.type and error.type) and Excalibur.EventSourcing.MaterializedViews (materialized_view.staleness, materialized_view.state, materialized_view.refresh.duration, materialized_view.refresh.failures).

FaultObservableFramework behaviourYour action
A projection handler throws on an event113103; excalibur.projection.error.count rises with the error.type tagThe error is recorded. The projection does not silently advance past an event it did not apply.Fix the handler, then rebuild the projection.
A projection falls behind the stream113102 projection behind; materialized_view.staleness climbs for viewsNothing — it catches up when it can.Reads are stale meanwhile. Treat staleness as an SLI, not an error.
An async projection host errors113205; individual event errors as 113204The host logs and continues; checkpoints are saved as 113203.A checkpoint that stops advancing while events arrive means the host is stuck, not slow.
A rebuild is requested while one is already running113108The second request is rejected, not queued.Wait for the first. GetStatusAsync reports progress.
An event errors during a rebuild113105Recorded; the rebuild continues.Review before trusting the rebuilt view.
A rebuild finds no store to persist into113107The rebuild produces nothing durable.Register a projection store.
No global-stream query is available to the async host113206The host cannot read the stream it projects from.The store in use does not offer a global stream. Choose a store that does, or use a different projection strategy.

Change data capture​

See CDC Troubleshooting for diagnosis; this table is the behaviour contract.

FaultObservableFramework behaviourYour action
No CDC processor is registered but the background service is running3110The service does nothing.Register a processor, or disable the service (3100 confirms it is disabled).
A processing cycle throws3102, then 3109 error backoffBacks off and retries. No checkpoint is written for a table that failed, so the next run resumes from the last durable position and redelivers.Duplicates on recovery are expected; the window is the whole run for that table.
Shutdown could not drain in time3105Stops; the next run resumes from the last checkpoint.None.
The stored position is no longer available in the source100743 stale position detected, then 100744 recovery attempt or 100746 recovery exhaustedApplies the configured StalePositionRecoveryStrategy: Throw stops, FallbackToEarliest replays, FallbackToLatest skips everything in between.Confirm which strategy you are running. FallbackToLatest is opt-in data loss.
A stale log sequence number is reset100942Detection resets the position for that table.Expect a replay.
A checkpoint was deliberately not written100742 checkpoint skippedThe position stays where it was, so the changes are redelivered.This is the guarantee holding. Nothing to do.
The in-memory idempotency filter is full100981 — new events will not be tracked for deduplicationThe filter stops tracking. Deduplication degrades; delivery does not stop.Raise the capacity, or move to a durable idempotency filter. Silent duplicate suppression ending is exactly the kind of degradation worth alerting on.
The replication connection drops (PostgreSQL)102308 processing error, 102309 fatal errorTransient errors retry; a fatal error stops the processor. If a consecutive-transient-failure bound is configured, exhausting it stops without writing a position.A stop with no position written means the next run replays rather than skips. That is the safe direction.

Sagas​

Meters: Excalibur.Dispatch.Sagas (dispatch.saga.started_total, dispatch.saga.completed_total, dispatch.saga.failed_total, dispatch.saga.compensated_total, dispatch.saga.duration, dispatch.saga.handler_duration) and the saga store's excalibur.saga.operations / excalibur.saga.operation_duration.

FaultObservableFramework behaviourYour action
A step fails122203 step failed, starting compensationCompensation runs for the steps already executed.Make compensations idempotent; they can run more than once.
Compensation completes122204; dispatch.saga.compensated_total risesThe saga ends compensated.A rising compensation rate is a business-level signal, not only an operational one.
A saga execution fails outright122205; dispatch.saga.failed_total risesThe saga ends failed.Compensation may be incomplete. Reconcile by hand.
A timeout could not be delivered121203Logged; the timeout batch continues.A saga waiting on an undelivered timeout waits forever. Alert on this.
A timeout's message type cannot be resolved or constructed121206, 121208, 121207That timeout is not delivered.Almost always a type renamed or removed by a deployment while timeouts were already scheduled.
A timeout was superseded before firing121209Not delivered — a newer schedule replaced it.Nothing.
The timeout service stops121205Scheduled timeouts stop firing until it restarts.Alert on this. A stopped timeout service is silent: sagas simply never advance.

Leader election and fencing​

Meter: Excalibur.LeaderElection — excalibur.leaderelection.acquisitions, excalibur.leaderelection.lease_duration, excalibur.leaderelection.is_leader.

FaultObservableFramework behaviourYour action
An instance cannot acquire the lockSQL Server 184002; the instance's excalibur.leaderelection.is_leader stays at zeroNormal for non-leaders.Alert when the sum of is_leader across instances is zero for longer than a lease plus grace — that is nobody leading, not one leader.
Renewal fails; leadership is lostSQL Server 184006 then 184008; Redis 183006 then 183009; Kubernetes 182015 / 182021 then 182019The instance relinquishes within its configured grace period. Another instance acquires.This is a partition healing correctly. Frequent transitions mean the lease is too short for your network.
The fencing-token mint failsSQL Server 184010; Redis 183011Fail-closed — leadership is not declared and the lock is released. A tenure without a valid token never becomes observable.Check the mint. On SQL Server and PostgreSQL it is a SEQUENCE; on Redis and Consul a counter key.
The fencing token space is exhausted180008 (in-memory), 181024 (Consul), 182105 (Kubernetes)Fail-closed: no token, no leadership.This requires a store reset, not a restart. See Disaster Recovery.
A superseded tenure tries to mark a message sent131229 fenced mark-sent refusedThe cycle aborts with no further store write. The message is left claimed for the current leader.Nothing. The fence is doing its job. A sustained stream of these means a stale process is still running, or the mint sits below the recorded high-water after a restore.
A superseded tenure tries to report a failure134008Nothing is recorded. The tenure should stand down rather than keep draining.As above.
A superseded tenure tries to dead-letter134009The outbox row is not destroyed. A newer tenure owns the message and will resolve it.Nothing.
A dead-letter entry was withdrawn after its fenced mark was refused131311 at WarningThe entry is removed, so nobody redrives a message that may yet be delivered.Nothing.
That withdrawal itself failed131312 at Error, naming the entry and the messageThe entry remains and describes a message this tenure no longer owns.Do not replay that entry. Confirm the message's fate, then remove the entry by hand.
A candidate cannot confirm its own leadershipConsul 181025 grace period elapsedRelinquishes within the grace period. The grace period is a hard upper bound on time-to-relinquish.Size the grace period against your worst-case network stall, not your average.

Capacity and quota exhaustion​

FaultObservableFramework behaviourYour action
The in-memory outbox store is full of undelivered messagesStageMessageAsync throwsIt refuses rather than evicting. Capacity is reclaimed only from messages whose delivery is over. The caller — whose own transaction has not committed — learns the message was not accepted.Size MaxMessages for the drain you actually run. A persistently full store is a drain that is not keeping up.
The inbox processing queue is saturatedThroughput flat against excalibur.inbox.operationsBounded by QueueCapacity, ProducerBatchSize, ConsumerBatchSize and PerRunTotal. Back-pressure, not loss.Raise the bounds, or add consumers.
The CDC idempotency filter is full100981Stops tracking new events. Deduplication degrades; delivery continues.Raise capacity or use a durable filter.
A cloud store throttles (request-unit or capacity limits)Provider retry policies engage; excalibur.outbox.store.operation_duration and excalibur.inbox.operation_duration rise before error counts doSDK-managed retry with backoff.Latency rises before anything fails — alert on the duration histograms, not only on errors. See Recovery Runbooks.
The database is out of space or resourcesProvider-specific transient codes (PostgreSQL 53xxx, SQL Server log-full errors)Treated as transient and retried. Nothing is marked sent that was not sent.Retrying will not create disk. Alert on sustained retry, not on individual errors.

Credentials and key access​

FaultObservableFramework behaviourYour action
The key-management service is unreachable or refuses access92218 key management failed, then 92214 with the collected failures. The encryption health check reports Unhealthy (a slow key-management response reports Degraded with 92215 instead).The health check result changes, so an orchestrator can stop routing to the instance. Nothing else stops.Wire the health check — this path runs only while a key-management provider is registered and VerifyKeyManagement is on, which is the default. An expired credential is otherwise visible only as decryption failures.
A field cannot be decrypted92206 decryption error for fieldReported rather than silently skipped.Usually a key version the store no longer holds — see Disaster Recovery.
A field that should have been decrypted was read still encrypted92974Reported.A registration or configuration gap, not a key problem.
Scheduled key rotation fails92550 rotation failed, 92551 rotation exception, 92544 rotation check errorThe existing key stays in use. Nothing breaks immediately.Alert on this. A rotation that silently stops means key age grows without bound — 92223 reports a key past its maximum age.
Re-encryption after a rotation fails92209, per item 92210Items stay under the old key and remain readable.Re-run the migration. Do not retire the old key first.
Escrow backup or recovery fails92942Fails closed; no key material is released below the configured custodian threshold.Recovery authorization, not confidentiality — see Disaster Recovery.
Erasure could not delete a key92708; verification reports 92720 key not deleted with the current statusThe erasure is not reported complete.This is a compliance obligation, not a retry. Resolve before closing the request.

Clock skew​

Clock skew is not a fault the framework reports as an event; it is a condition that changes how other guarantees behave. What matters is which clock each decision uses.

DecisionClock usedWhat skew does
Outbox retry backoff floorThe store's clock. Each store composes the caller's schedule with the floor over durations anchored to the server clock.No dispatcher clock reaches the persisted gate. Dispatcher skew does not shorten the floor.
Leader lease expiry (Kubernetes)The API server's stamp on renewTime.Candidate skew does not affect the comparison.
Leader lease expiry (MongoDB)The database's clock, compared server-side.As above.
Leader lease expiry (Redis)The Redis server's TTL.Expiry safety never rests on a client clock. The client-side grace governs only how fast a candidate self-demotes after a renewal fault.
Leader lease expiry (SQL Server, PostgreSQL)Elapsed monotonic time on the candidate, against a session-scoped lock.Monotonic time is unaffected by wall-clock adjustment. A large NTP step does not extend a lease.
Inbox lease and message time-to-liveThe store's clock.Skew between application hosts does not widen the duplicate window; skew between database replicas can.

The practical guidance is narrow, because the design already removed most of the exposure: keep your database and broker clocks disciplined, and size leader-election grace periods against the worst stall you will tolerate rather than the average. Application-host clock skew is, by construction, not load-bearing for any of the decisions above.

Schema migration​

Relevant during an upgrade; see the upgrade runbook.

FaultObservableFramework behaviourYour action
Pending migrations found at startup114414Reported, with the list.Apply them before the new version writes.
The migration lock cannot be taken114412The migration does not run. The lock serializes concurrently starting instances, so this usually means another instance is migrating.Wait. If nothing is migrating, a lock is stranded.
A migration script is missing114418The migration cannot proceed.The applied history references a script this build does not carry — usually a rollback to a version that predates it.
An already-applied script's checksum has changed114416 checksum driftReported. A script that was edited after being applied no longer describes the database you have.Never edit an applied script. Add a new one.
A migration fails114403Reported. The database is in whatever state the script left it.Restore, or fix forward, deliberately.
A rollback fails114407Reported.The database is between two states. This is a restore, not a retry.

See Also​