Skip to main content
When we fix a bug that taught us something, we write a postmortem. One file per bug. Kept indefinitely. Unlike the Known Issues pages — which shrink as we fix things — postmortems only grow. Six months from now, a new maintainer should be able to read these and understand how this codebase has failed before. That’s the point.

What’s in each one

Every postmortem follows the same five-part shape:
  • Symptom — what a user, operator, or downstream system saw when the bug fired. Outside-in, not code-first.
  • Root cause — the specific code path and, crucially, why it was wrong. The mental model that led to the mistake, not just the line that got flipped.
  • Fix — what changed, with links to the commits.
  • Why the tests didn’t catch it — an honest paragraph. The single most useful section in the whole file.
  • Class of bug — where else to watch — the pattern generalized, with specific other code paths that might hide the same shape. This is what turns a log entry into a tool.
Some bugs don’t warrant this. Typos, dependency bumps, style cleanups, one-off bugs with no generalizable lesson — they live in the commit message and nowhere else. The bar for writing a postmortem is: does the fix teach a future reader something about this codebase’s failure modes?

Bindu Core (5)

DID signature fails open

The middleware noted “can’t verify” and let the request through anyway. Textbook fail-open.

IDOR on tasks and contexts

Any authenticated caller could read, cancel, or clear another caller’s tasks. Row-level auth was just… missing.

Pydantic populate_by_name mismatch

Types accepted camelCase over the wire but rejected snake_case internally. Quiet contract drift.

DID document endpoint returned raw dict

The resolver returned the wrong shape — crashed clients expecting a proper DID document.

DID signature overbroad exceptions

A bare except Exception turned real bugs into silent false-negatives.

Gateway (6)

Compaction concurrent races

Two parallel compactions could tangle a session’s history. An in-process promise cache now dedupes them.

Compaction lossy second pass

Re-compacting a summary dropped content. The second pass assumed the input was raw, not already-compacted.

Compaction mid-turn cut

Compaction could slice between a tool_use and its tool_result, breaking the LLM’s view of the conversation.

SpawnReader fiber leak

A long-running fiber stayed alive after the request completed. Memory crept up over time.

SSE cross-contamination

Two sessions could see each other’s SSE events through a shared pubsub filter.

Timing-unsafe token compare

A plain == on secret tokens opened a small timing side-channel. Fixed with a constant-time compare.

SDKs and Frontend

Nothing yet. When the TypeScript SDK’s review pass lands and ships fixes worth remembering, they’ll appear under postmortems/sdk/. Same for the frontend.

Why this archive exists

A few beliefs that led here:
  • Commit messages carry the tactical detail of a fix. They’re good at “what changed.” They’re bad at “what was the thinking that let this slip through.”
  • GitHub Issues are the source of truth for status — open, closed, owned, milestoned. They’re bad at “what did we actually learn.”
  • This archive is the source of truth for lessons. Every file names the pattern, every file lists other places the same shape could hide. A pattern named once prevents the next occurrence.
If you’re a new contributor, reading these is one of the faster ways to get up to speed on how this codebase fails. More useful than skimming random files in the main branch, because these have context you can’t see from the current state of the code.