Agent-Board no longer loses events. A case study.

Bug found on · fix merged on · built with Claude Code

Agent-Board shows live what my Claude Code agents are doing. Every step of an agent lands as one line in a file, and the board reads that file as it grows. When it gets too big, I trim it. That is exactly where events got lost.

The problem

Every Claude Code hook appends one line of JSON to events.jsonl. Above 5 MB the board keeps only the newest lines. Two things went wrong: concurrent hooks wrote into a file that was just being rewritten, which produced half JSON lines. And the server that streams the file to the browser lost the event right after a trim, for example an agent finished.

Decision 1: a lock, but no dependency

Agent-Board has zero dependencies, and I want to keep it that way. Instead of a lock library I use the fact that creating a directory is atomic: whoever succeeds with mkdir first may trim and write. Everyone else waits briefly.

Decision 2: better to write without the lock than to block the agent

A hook must never hold Claude Code up. So it waits at most 2 seconds for the lock and then writes anyway. A lock left behind by a crashed process is removed as stale after 10 seconds. That is a deliberate trade-off: in a very rare case a line can break again, but an agent never hangs.

Decision 3: temp file first, then rename

The trimmed file is written to a temp file first and then renamed in one step. That way the server never reads a half-written file. On Windows the rename fails while the server has the file open; then the trim simply happens next time instead of throwing an error. Also, only the current user may read and write the file, because it shows what you are working on.

How I know it holds

Three new tests: the server resumes at the right place after a trim, 30 concurrent hooks right at the 5 MB limit produce no half line, and a real live stream to the browser loses no event. While merging I noticed a test that sometimes failed for no reason because it waited a fixed 400 ms. Now it waits until the server is actually ready.

74tests green after the merge
3new tests for trimming
0new dependencies

What I take away

Concurrency is where bugs hide, because they only show up sometimes. A test that fails only sometimes is not bad luck, it is a hint. And a good fix writes down its trade-off, so nobody mistakes it for a bug later.

The code, the tests and the pull request are public. Feel free to check.