---
title: 'CHR0031: Reactor must not have mutable state'
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

## Rule Description

A class implementing `IReactor` declares a mutable instance field or settable property. Reactors observe events and produce side effects; Chronicle re-creates and replays them, so mutable instance state is unreliable and leaks context between invocations.

Keep reactors stateless: express dependencies as `readonly`, primary-constructor-injected fields, and derive everything else from the event and event context.

## Severity

Warning

## Example

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;

public class Chr0031OrderCounter : IReactor
{
    // Warning CHR0031: Reactor declares mutable state '_count'; reactors are re-created and
    // replayed by Chronicle, so instance state is unreliable and leaks context between
    // invocations. Use readonly, primary-constructor-injected dependencies.
    int _count;

    public Task OrderPlaced(Chr0031OrderPlaced @event, EventContext context)
    {
        _count++;
        return Task.CompletedTask;
    }
}

[EventType]
public record Chr0031OrderPlaced(string OrderNumber);
```

</TabItem>
</Tabs>

## Why This Rule Exists

A reactor has no reliable lifetime you can accumulate state across. Chronicle re-creates reactor instances and replays event streams through them, and a single event source may be processed more than once (recovery, rewind, redaction, revision). A mutable field or settable property therefore holds a value that depends on which events happened to run through this particular instance — a counter that is wrong after a replay, or a value that bleeds from one event source into the next.

Everything a reactor needs is already available: the collaborators it calls (injected once through the primary constructor as `readonly` fields) and the facts of the event it is handling (the event payload and `EventContext`). Anything derived should be computed from those inputs each time the handler runs, not stored on the instance. `static const`, `static readonly`, `readonly`, and `init`-only members are fine — the rule flags only mutable non-static fields and properties with a non-`init` setter.

## Related Rules

- [CHR0032: Reactor must not access storage directly](/chronicle/code-analysis/chr0032/) — reactors read state through read models.
- **CHR0022** — a reactor handler returning side-effect events must be marked `[OnceOnly]`.
