---
title: "My first patch to Ergo"
description: "A message sent to multiple IRC targets shared one ID. Ergo's delete lookup ignored the target channel, so I patched it."
pubDate: "2026-10-03"
heroImage: "/slirc-ergo-hero-false-alarm.png"
series: "Building Slirc"
part: 3
---

<aside class="update-callout" role="note">
<p class="update-callout-label"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>Update, October 6 · False alarm</p>
<p>Ergo gives each target its own msgid, so <code>PRIVMSG #a,#b :hello</code> creates two messages and the bug below can't happen. Slingamn, Ergo's maintainer, <a href="https://github.com/ergochat/ergo/pull/2462#issuecomment-5997674369">showed it with one command</a>.</p>
<p>My test passed because it wrote the duplicate rows straight into the database. I closed the PR. Next time I'll reproduce the bug on a real server first.</p>
<p>The post below stays as I wrote it. Ergo didn't have a bug, my method did. <strong>Lesson learned.</strong></p>
</aside>

I was testing message deletion in **Slirc** when I found an Ergo bug: deleting a message from one channel could affect a copy in another.

A message sent to multiple IRC targets gets one msgid, but Ergo stores one history row per target. `HistServ` replied, “Successfully deleted message.” It could have deleted the copy from another channel instead.

## One message, two channels

An IRC message has a `msgid`. You can send one message to more than one channel at once, like `PRIVMSG #a,#b :hello`. Ergo stores a history row in each channel, with the same msgid:

| Channel | msgid | History row |
|---|---|---|
| #a | m42 | 101 |
| #b | m42 | 102 |

The old database lookup searched for `m42` and stopped at the first row. It didn't check which channel the delete named. An operator's `HistServ` delete for `#a` could remove the `#b` copy and still report success, leaving `#a` untouched. For regular users, finding the wrong copy could make a valid delete fail its permission check.

## Keep the channel in the lookup

The fix passes the channel through both lookup and deletion. Ergo now matches the msgid and channel name together, then removes the matching row from that channel. A delete for `#a` leaves the copy in `#b` alone. I made the same change for SQLite, MySQL and PostgreSQL.

The regression test stores the same msgid in two channels, deletes it from one, then checks that the other copy is still there. The patch is in [Ergo PR #2462](https://github.com/ergochat/ergo/pull/2462).

```diff
diff --git a/irc/sqlite/history.go b/irc/sqlite/history.go
@@ DeleteMsgid
-_, id, _, _, err := s.lookupMsgid(ctx, msgid, true)
+rows, err := s.db.QueryContext(ctx, `SELECT history.id FROM history
+    INNER JOIN sequence ON history.id = sequence.history_id
+    WHERE sequence.target = ? AND history.msgid = ?;`, target, msgid)
@@
-err = s.deleteHistoryIDs(ctx, []uint64{id})
+err = s.deleteHistoryIDs(ctx, ids)

--- a/irc/sqlite/delete_test.go
+++ b/irc/sqlite/delete_test.go
@@
+db.DeleteMsgid("#unregistered", "shared-msgid")
+// The #other copy remains; the #unregistered copy is gone.
```

That fix belongs in Ergo. It's the server's job to keep the permission check and history deletion on the same channel. It doesn't change who can delete messages; it makes sure the request affects the channel it was authorized for.

## So, that means you know Go, right?

Not really. I don't know Go. But I knew the bug: deleting the message in `#a` should leave its copy in `#b`. GPT-6 Luna helped me turn that into a patch. I reviewed the diff and regression test to make sure the other target stayed untouched.

That's what I like about working with AI. I can explain what I need, understand the trade-offs, and review the code well enough to decide whether I'm comfortable submitting it. I don't need to know the language already. This time, I learned enough to send a PR to Ergo.

The PR is still open. I’ve published patched Ergo images for AMD64 and ARM64 and deployed them to Slirc.org.

## What's next

- **October 6th:** pre-launch. The waiting list gets into app.slirc.org.
- **October 12th:** the source code goes public.

[Join the waiting list](https://slirc.org/#waitlist), and see you on the 6th.
