Messages and backfill
The bot keeps a copy of the messages in your server: new ones as they arrive, old history in the background. Reminders use the copy to tell whether you have posted. Summaries, answers to questions and the embedding sweep (the background job that builds search-by-meaning data, see AI and embeddings) read it too.
The copy lives in your Postgres database, in the messages and channels tables, see Data.
No command shows the stored text back word for word. A summary restates what was said and links to the original messages, only for channels the reader can see.
What is stored
Section titled “What is stored”Only messages in the server set by DISCORD_GUILD_ID. DMs and other servers are ignored.
| Discord channel | Stored as |
|---|---|
| Text channel | text |
| Announcement channel | announcement |
| Forum or media channel | forum (the channel row only; its posts are threads) |
| Public thread, announcement thread, forum post | thread, with its parent channel recorded |
| Voice, stage, category, private thread, anything else | Not stored. That includes the text chat of a voice channel, so the notice and summary that voice notes post there are not copied |
Only normal messages and replies are stored. Joins, pins, thread starters, replies to slash commands and other types are skipped. Messages from bots are stored and flagged, but never embedded and, by default, left out of search.
For each message the bot keeps:
| Kept | Notes |
|---|---|
| Text | A forward with no text of its own uses the forwarded text. NUL characters are removed. |
| Author id and display name | Server nickname, else global name, else username. Cut to 100 characters. |
| Bot flag and message type | The type is Discord’s number for a normal message or a reply. |
| Sent time, edited time, deleted time | |
| Attachments | A yes or no and a count, including attachments on a forward. Never the files. |
| Reply target | Only for replies, not for forwards. |
| Channel | For a thread message, the thread. |
Embeds and reactions are not stored. Channel names are cut to 100 characters.
Live capture
Section titled “Live capture”The bot listens for new, edited and deleted messages (single and bulk) and for channel and thread changes. A new or renamed channel or thread saves its name, kind and parent. The first stored message of a channel also saves the channel, if the bot has not seen it since it started.
A handler failure is logged as [messages] <label> failed: ... and never stops the bot. The labels are messageCreate, messageUpdate, messageDelete, messageBulkDelete, channelCreate, channelUpdate, threadCreate, threadUpdate, channelDelete and threadDelete.
- New and edited messages from people are queued for embedding when embeddings are active. An embedding is a vector, a list of numbers that captures what the text means, used for search by meaning.
- A new message that was stored is also offered to the ticket prompt check (the part of the bot that decides whether to offer to file a Linear ticket), see Ticket prompts. Edits and backfilled messages never are.
- The meeting transcript feature listens to new messages separately, stored or not. A file uploaded to the meetings channel is never copied into the store, but text typed there and the bot’s own summary post are stored, and typed text is embedded. See Meeting transcripts. Voice notes listen to audio, not messages.
An edit updates the text, attachment info and edited time. Author, type, reply target and sent time never change after the first save. Three rules protect the copy:
- A deleted message is never brought back by a late edit.
- An older edit never overwrites a newer one.
- An edit that changes nothing is not written.
An edit that blanks the text also removes the message’s vector, so the old words cannot be found. If Discord sends an edit event with only part of the message, the bot fetches the full message first. If the message was deleted in between, the event is ignored.
Deletes
Section titled “Deletes”Deleting a message keeps the row but sets its deleted time and empties its text (the bot calls this scrubbing). Its vector is removed in the same step. Who wrote it and when stay.
A late save arriving within 10 minutes of a deletion is scrubbed again, so a race cannot resurrect the text. The bot remembers recent deletes in memory only, so a restart forgets them.
Deleting a channel or thread scrubs every message in it, and for a parent channel every message in its threads. The channel row is kept.
Other copies of the text go with the message:
- Ticket prompt drafts. Deleting a message clears the draft title, description and suggested team of its ticket prompt, even if the message was never stored. Deleting a channel clears the drafts for it and its threads. A ticket check still running for a deleted message or channel is abandoned.
- Question logs. Deleting the message that tagged the bot blanks that question and its trace. Deleting the message a question replied to blanks the trace but keeps the question. Deleting a channel blanks the questions asked there by tag, and those in its threads. See Asking the bot questions.
If the hook that abandons a running ticket check fails, it is logged as [messages] onDelete hook failed: ... or [messages] onChannelDelete hook failed: ... and the scrub still happens.
Backfill
Section titled “Backfill”Backfill reads old messages from Discord into the copy. It runs when Discord reports the bot ready, and again whenever the bot starts a new session after losing its connection. A connection that resumes replays the missed events instead. Only one runs at a time, and a request during a run runs afterward. On shutdown a running backfill stops and logs [backfill] aborted.
- Find channels. All server channels, active threads, and archived public threads of channels the bot can see. Archived private threads are not listed. If a parent’s archived threads cannot be listed, that is logged as
cannot list archived threads of #name, and those threads are left out of this run. The parent is still scanned. - Skip what it cannot read. A channel needs both the View Channels and Read Message History Discord permissions. Others are logged in one
[backfill] cannot read: ...line and skipped. Forum channels have no messages of their own and are never scanned. - Look for deleted channels, unless an archived-thread listing failed. See Deleted channels.
- Scan three channels at a time, 100 messages per request.
Each channel gets up to three passes. They use the channel’s cursors and complete flag, explained in Cursors and restarts:
| Pass | Runs when | What it does |
|---|---|---|
| Catch up | The channel has been seen before and Discord reports a newer message than the newest stored one | Walks forward from the newest stored message. |
| History | The channel is not yet marked complete | Walks backward from the oldest stored message, or from the channel’s newest message if none is stored. A short or empty page marks it complete. |
| Rescan | Not during shutdown, and the channel’s newest message or a stored message is within the last 7 days. Skipped when Discord reports no newest message | Re-reads the last 7 days. |
A first-time channel skips catch up and walks backward to its beginning.
Cursors and restarts
Section titled “Cursors and restarts”Each channel row remembers the ids of its newest and oldest stored messages (its cursors) and a flag that says the history walk has reached the beginning (the channel is marked complete). Backfill moves the cursors after each page is saved, so a crash re-reads at most one page. Live handlers never move them. A restart resumes from the saved cursors. A large server can take hours the first time.
Backfill does not queue embeddings. Old messages get their vectors from the sweep, see AI and embeddings.
The 7-day rescan
Section titled “The 7-day rescan”The bot cannot see deletes that happen while it is offline. So every backfill re-reads the last 7 days of each scanned channel that has a message in that window. Discord’s newest-message pointer can be out of date, so stored messages inside the window also count. Edits made in the gap are saved. Stored messages in the window that Discord no longer returns are scrubbed as deletes, and the run logs [backfill] <channel id>: marked N missed deletes.
Scrubbing cannot be undone, so:
- The list of stored messages is taken before the walk, from the window start with no upper end. A message the live handler stores during the walk is never in it, so it can never be scrubbed.
- It scrubs only if the walk finished and a fresh check shows the bot still has View Channels and Read Message History. An empty window can scrub, but only after that check passes. If the check fails, it logs
[backfill] <channel id>: history unreadable after the walk, skipping delete reconciliationand scrubs nothing.
Deleted channels. Before scanning, the bot compares stored channels that still hold messages (directly or in a thread) with the channels Discord lists. For each one missing, it asks Discord directly. Only Discord’s Unknown Channel error counts as deleted, and the channel is scrubbed like a live channel delete: its threads, their ticket drafts and the question logs too. Any other error leaves the channel alone and logs [backfill] cannot confirm channel <id>: .... If Discord returns the channel, nothing happens.
Threads are not checked this way. A thread deleted while the bot was offline keeps its stored text unless its parent channel was deleted too.
Failures and progress
Section titled “Failures and progress”A channel that fails is logged as [backfill] #name failed: ... and retried once after 30 seconds. If it fails again it is logged as failed again and tried on the next start or new session. An error that stops the whole run is logged as [backfill] fatal: ....
[backfill] 42 channels to scan, 3 unreadable, 0 archived-thread listings failed[backfill] #general: 1000 messages scanned[backfill] 10/42 channels (0 failed)[backfill] completeThe messages scanned line repeats every 1,000 messages of a channel. When some channels failed twice, the last line reads [backfill] complete, N channels failed (retry on next start or reconnect): <names>. When a deleted channel is found, one more line appears before scanning starts:
[backfill] marked 1 deleted channels: 123456789012345678Members
Section titled “Members”Member sync runs when Discord reports the bot ready and whenever a new session starts. It stores every human member with their display name. A failure is logged as [members] sync failed: ..., and the next session tries again.
- A member who joins or changes their name is saved.
- A member who leaves gets a left time. Their row and messages are kept.
- A member who returns is marked present again.
- After a full sync, anyone stored but not in the server’s list is marked as left. An empty list is ignored, so a failed fetch cannot mark everyone as left.
- Joins and leaves during a sync are respected: a member who leaves mid-sync stays left, and one who joins mid-sync is not marked as left. The
[members] synced N membersline counts the human members in the list, minus any who left during the sync. It is not the number of rows that changed.
Members marked as left get no reminders and no one-time setup DM, and their pending follow-ups are skipped.