Symptom → likely cause → fix for the common ways a Cratis application slice misbehaves. Each row routes to the rule/skill that owns the detail — start there. Reproduce a real defect with a spec before and after the fix.
Backend / Chronicle
| Symptom | Likely cause | Fix → |
|---|---|---|
| Read model query returns empty / stale | projection not wired: event→read-model property names differ (AutoMap only maps matching names), or a default value masks missing wiring | add-projection, cratis-readmodel; [SetFrom<T>] for name diffs; no default values on [ReadModel] props |
| App crashes on startup with a projection error | duplicate [FromEvent<T>] for the same event on one read model |
add-projection (one [FromEvent<T>] per event) |
| Command always "not successful" with no validation errors | [Roles]/authorization: an unauthorized result isn't successful and carries no validation errors |
auth-and-identity, cratis-command; assert ShouldNotBeAuthorized() |
| Command rejected unexpectedly | a CommandValidator/ConceptValidator rule, or Provide() short-circuited with ValidationResult.Error |
add-business-rule, cratis-command |
| Handler returns HTTP 500 instead of rejecting | threw from Provide()/Handle() for a business rule (that's an exception, not a rejection) |
return Result<TEvent, ValidationResult> — add-business-rule |
| Reactor's returned events never appended | returned EventForEventSourceId unconditionally on a Chronicle version without reactor support for returned events |
return event objects; use EventForEventSourceId only to target a different event source — reactors.md |
| Projection/reactor stops processing ("quarantined") | a handler threw; the partition paused and the observer quarantined (does not auto-resume) | read the failure off the server first — inspect-running-chronicle; then fix the handler (make it idempotent) and replay — reactors.md |
| Duplicate side effect on replay | non-idempotent reactor without [OnceOnly] |
mark the handler [OnceOnly] — reactors.md |
| Chronicle analyzer warns on an event property | a nullable event property | model the optional fact as a separate event — vertical-slices.md, event-modeling |
| A read model needs a field from another slice | wrong stream boundary / missing event | re-model (information completeness) — event-modeling; never cross-read another read model at runtime |
Frontend / proxies
| Symptom | Likely cause | Fix → |
|---|---|---|
TS can't find the command/query proxy (Cannot find module) |
proxies regenerate on a Debug build; you only built Release | build Debug — new-vertical-slice (backend phase) |
| Edited a generated file and it reverted | generated proxies (// @generated by Cratis) are never hand-edited |
fix the C# source and rebuild — general.md |
DataPage won't show live data |
passed a snapshot query, or expected a non-existent observableQuery prop |
pass the observable query to the single query prop (auto-detected) — cratis-react-page, components.md |
| Dialog/dropdown renders behind an overlay | used raw primereact/dialog or primereact/dropdown |
use the Cratis wrappers from @cratis/components/* — dialogs.md, components.md |
Specs
| Symptom | Likely cause | Fix → |
|---|---|---|
| Spec code leaks into a Release build | spec not wrapped in #if DEBUG … #endif |
wrap the file — specs.scenarios.csharp.md |
| Off-by-one in event-tail assertions | sequence numbers are zero-based (tail of one event is 0) |
specs.scenarios.csharp.md |
| Order-dependent flake on a uniqueness rule | a hardcoded value collides across tests | use a per-test value (Guid.NewGuid()); don't reach for [Collection] for collisions — specs.scenarios.csharp.md |
When the symptom is only visible on a running server
Everything above is diagnosed from the code. When the read model is stale in a deployed store and the same slice behaves locally, the answer is in the server's own state rather than the source — a failed partition carrying the exception that stopped it, an observer that never registered, an event that was never appended. Use inspect-running-chronicle; the cratis CLI reads all of it.
The distinction that matters: a failed partition does not retry itself, so what looks like "the projection is slow" is usually "the projection stopped, permanently, with a recorded reason nobody has read".
When nothing here fits
Re-read the owning rule (vertical-slices.md, reactors.md, or the cratis-readmodel skill), confirm the build is clean in both Debug and Release, and reproduce the symptom with a spec. Don't infer framework behavior from package internals — if the rules/skills don't answer it, ask.