Skip to content

feat(release-bot): scheduled Discord announcements for releases and videos - #282

Open
Anmol-Baranwal wants to merge 3 commits into
CopilotKit:mainfrom
Anmol-Baranwal:feat/release-discord-bot
Open

Anmol-Baranwal wants to merge 3 commits into
CopilotKit:mainfrom
Anmol-Baranwal:feat/release-discord-bot

Conversation

@Anmol-Baranwal

Copy link
Copy Markdown

Posts CopilotKit, AG-UI and OpenBot releases to their Discord channels, plus new videos from the CopilotKit YouTube channel. Runs once a day on Railway.

Release notes are not postable as they are. AG-UI's are pages of package tables, CopilotKit's are often one sentence. So the bot sends the notes and the recent commits to OpenAI and gets back a few lines on what actually changed.

How it works

sources.ts is the whole config surface. One entry per repo: which repo, which channel, which role to ping, which tags count, and how to title the post. Adding a repo is an entry there plus a channel id in the env. Tag filters live in code rather than env because they are decisions about what is worth announcing, and they need a reason written next to them. That is also what keeps junk like pr-6517-visuals and vundefined out of the channel.

The rest is github.ts (read releases and the commits between two of them), youtube.ts (the channel feed), summarize.ts (the OpenAI call and what to do when it fails), discord.ts (read channel history, post), watermark.ts (what is still pending), and index.ts wiring it together.

No database. Every message it posts ends with the release URL, so to know what it has already announced it reads its own recent messages in the channel and looks at those URLs. The channel is the truth. If it is in there, it went out. Delete a message and it gets posted again, which is handy. Nothing to migrate if a repo moves.

It posts what is not in the channel yet, rather than what shipped since the last run, because there is no record of when the last run was. The 30 day window is the catch-up range: if a run fails, or the service is down for a couple of days, the next run picks up what it missed. On a normal day nothing is new and it posts nothing.

That is also why it has to read channel history back past 30 days. If it read less, a release could be too old for the bot to see its own announcement but still inside the window, so it would get posted again every day.

CopilotKit also ships v1.73.0, channels/v0.11.0 and angular/v0.5.2 from the same repo. Those are separate release lines, so the bot tracks them separately. Otherwise posting v1.73.0 makes it think everything older is done, and a channels/ release from an hour earlier gets skipped.

Each release is its own message, oldest first, at most 2 per source per run, so a backlog catches up over a few days instead of dumping into the channel at once.

First run in a channel

Say you point it at a channel that has nothing from it yet. It does not replay the last 30 days. It posts the current version of each thing the repo ships, and nothing older.

For CopilotKit that is three messages: the latest CopilotKit release, the latest Channels SDK release and the latest Angular SDK release. Those go out over two days, since it posts at most two per run. AG-UI and OpenBot each ship one thing, so they get one message.

After that it is caught up and stays quiet until something new ships.

A new channel should not get a month of history dumped into it, and it should not be silent about what the current version is either.

What it does when things go wrong

Bot was off for a while. Picks up where it left off, 2 per source per run, oldest first. Anything that aged past 30 days while it was down is not posted.

A release will not summarize. Temporary failure, it holds that source in place and tries again next run, so nothing jumps the queue. If the release itself is the problem, it posts the link with no summary rather than blocking everything behind it. If the key or model is wrong it stops the run and says so, instead of filling the channel with "Summary unavailable" and losing those releases for good.

A message gets deleted. That release gets posted again next run. The channel is the record, so this is how you force a repost.

Nothing actually shipped. Releases that are only dependency bumps, CI or version metadata get skipped. It checks that against the commits rather than taking the model's word for it.

Open questions for review

  • Should this be its own Railway service, or part of the existing worker? It is standalone right now with no shared deps.
  • Once a day at 08:00 UTC. Picked arbitrarily.
  • 2 per source per run, once a day. On average that is plenty: roughly 8 releases a week across the three repos against room for 42. But CopilotKit often ships 3 to 5 in a single day, so a busy day takes 2 or 3 days to clear and an announcement can land well after the release. Nothing is lost, it always catches up. Raising the cap or running every 6 hours would both fix it. I picked 2 before I looked at the per-day numbers, so this one is worth a second opinion.
  • Tag filters in sources.ts are my read of what is worth announcing. intelligence-*, the per-adapter channels-teams/ lines and python-sdk/ are all skipped. Say if any of those should go out.

One shared file changed

.dockerignore is repo wide, so this is the one change that is not scoped to the new app.

Docker only applies a rule at the top level unless it starts with **/. The old .env* line therefore covered the root .env and nothing else, so apps/release-bot/.env and apps/discord-mcp/.env were both being uploaded into the build context.

Neither reached a shipped image, since every final stage copies only build output, but they should not have been going at all. The rules now match at any depth. The only tracked files affected are the three .env.example files, and those are added back on the next line. Other services are unaffected: they all build from inside Docker and none of them read host build output.

Happy to pull this into a separate PR if you would rather keep this one scoped.

Tested

199 tests, covering how it reads a channel to work out what it already posted, tracking each product separately, deciding what is still owed, tag filtering, what it does on each kind of OpenAI failure, building a message and trimming it to fit, retries and rate limits on Discord and GitHub, and parsing the YouTube feed.

Also ran it live against a test Discord server: posted, held the rest back, went quiet once caught up, and re-posted a release after its message was deleted.

The README covers setup: env vars, the Discord permissions it needs and why, the dry run flag, the full list of what is announced and what is skipped, and the Railway config.

Anmol-Baranwal and others added 3 commits September 22, 2026 17:25
Posts new GitHub releases and YouTube videos into the CopilotKit, AG-UI
and OpenBot channels. Runs as a Railway cron job.

Release notes are not forwarded. Each release is paired with the commits
since the previous one and rewritten into a few lines about what changed,
with outside contributors credited by name.

There is no state file. Every announcement ends with its source URL, so
the bot reads its own recent messages to find what it has already posted
and announces only what is newer, oldest first. Running twice posts
nothing the second time, and a crash mid-batch cannot cause a repeat.

Sources are configured in src/sources.ts, one entry per repository, with
the tag filter and the reason for it written together. Adding a repo is
an entry there and a channel id in the environment.

Covered by 75 tests across the watermark, the 2000-character budget, the
GitHub and YouTube parsing, and the summarizer's skip handling.
- read channel history far enough back to cover the release window
- treat out-of-credit, truncated and unrecognised OpenAI errors as what
  they are, instead of retrying forever or giving up permanently
- keep reading compare pages after one fails and never skip a release
  based on a partial commit list
- bump Node past the corepack version that fails the Docker build

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant