You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Multiple off-by-one errors and silent reminder deletion with no user notification in reminders.py #3507
Additional bugs found in bot/exts/utils/reminders.py
Bug 1: Off-by-one — users can create 6 reminders despite a limit of 5
Current code (reminders.py line 478):
MAXIMUM_REMINDERS=5iflen(active_reminders) >MAXIMUM_REMINDERS:
awaitsend_denial(ctx, "You have too many active reminders!")
return
> 5 only evaluates to True once the user already has 6 reminders in the database — meaning the 6th is always written before the check can block it. The denial never fires on the 6th attempt.
Fix:
iflen(active_reminders) >=MAXIMUM_REMINDERS:
awaitsend_denial(ctx, "You have too many active reminders!")
return
Steps to reproduce (as a regular member in #bot-commands):
Run each of the following one at a time:
!remind new 7d reminder 1
!remind new 7d reminder 2
!remind new 7d reminder 3
!remind new 7d reminder 4
!remind new 7d reminder 5
!remind new 7d reminder 6
All 6 succeed with a confirmation embed — the denial is never shown
Run !remind list to confirm all 6 are saved
Expected: the bot sends a denial on the 6th attempt and does not save it
Bug 2: Off-by-one — users can delete 6 reminders at once despite a limit of 5
The docstring explicitly states the limit is 5, but the same > vs >= mistake is present:
Current code (reminders.py line 660-663):
asyncdefdelete_reminder(self, ctx: Context, ids: Greedy[int]) ->None:
"""Delete up to (and including) 5 of your active reminders."""iflen(ids) >5:
awaitsend_denial(ctx, "You can only delete a maximum of 5 reminders at once.")
return
> 5 is only True when 6 or more IDs are passed, so passing exactly 6 IDs is accepted and all 6 are deleted. The docstring and the behaviour directly contradict each other.
Fix:
asyncdefdelete_reminder(self, ctx: Context, ids: Greedy[int]) ->None:
"""Delete up to (and including) 5 of your active reminders."""iflen(ids) >=5:
awaitsend_denial(ctx, "You can only delete a maximum of 5 reminders at once.")
return
Steps to reproduce (as a regular member in #bot-commands):
First create 6 reminders using the steps in Bug 1 above
The bot confirms all 6 were deleted — no denial is shown
Expected: the bot sends a denial when more than 5 IDs are provided and deletes nothing
Bug 3: Reminders are silently deleted with no user notification when the bot loses channel cache
Current code (reminders.py line 248-258):
defensure_valid_reminder(self, reminder: dict) ->tuple[bool, discord.TextChannel]:
"""Ensure reminder channel can be fetched otherwise delete the reminder."""channel=self.bot.get_channel(reminder["channel_id"])
is_valid=Trueifnotchannel:
is_valid=Falselog.info(
f"Reminder {reminder['id']} invalid: "f"Channel {reminder['channel_id']}={channel}."
)
scheduling.create_task(self.bot.api_client.delete(f"bot/reminders/{reminder['id']}"))
returnis_valid, channel
self.bot.get_channel() only checks the local cache — it does not make a Discord API call. If the bot restarts or a channel temporarily falls out of cache during an outage, get_channel() returns None even though the channel still exists and is perfectly valid. The reminder is then permanently deleted from the database with no DM, no message, and no indication to the user that their reminder was lost. The content is gone with no way to recover it.
Fix: use fetch_channel() to fall back to the Discord API before giving up, and DM the user if the reminder truly cannot be delivered:
asyncdefensure_valid_reminder(self, reminder: dict) ->tuple[bool, discord.TextChannel|None]:
"""Ensure reminder channel can be fetched otherwise notify the user and delete the reminder."""channel=self.bot.get_channel(reminder["channel_id"])
ifnotchannel:
try:
channel=awaitself.bot.fetch_channel(reminder["channel_id"])
exceptdiscord.HTTPException:
log.info(
f"Reminder {reminder['id']} invalid: "f"Channel {reminder['channel_id']} could not be fetched."
)
user=self.bot.get_user(reminder["author"])
ifuser:
awaituser.send(
f"Your reminder could not be delivered because the channel it was set in is no longer accessible.\n"f"**Reminder content:** {reminder['content']}"
)
scheduling.create_task(self.bot.api_client.delete(f"bot/reminders/{reminder['id']}"))
returnFalse, NonereturnTrue, channel
Steps to reproduce:
Set a reminder with !remind new 10m check this
Have the bot restart before the 10 minutes are up
When the reminder time arrives, nothing happens — the reminder was silently deleted on restart and the user receives no notification
Expected: the user receives a DM with their reminder content explaining it could not be delivered, so they can recreate it
I'd love to open a PR to fix all of the bugs outlined above if I'm able to be assigned. I'm happy to provide image proof reproducing each one if needed before any work begins. Thanks for your time!
changed the title [-]Off-by-one error allows regular users to create 6 reminders despite a limit of 5[/-][+]Multiple off-by-one errors and silent reminder deletion with no user notification in reminders.py[/+]on May 5, 2026
For 3, generally all channels should be cached, so it isn't a massive issue exactly as described, but it is still an issue as the channel may simply not exist. This has an existing issue at #3015. I think DMing sounds sensible. People can also react to reminders to also be reminded, should they be DMed as well?
Activity
oliveman-au commentedon May 5, 2026
Additional bugs found in
bot/exts/utils/reminders.pyBug 1: Off-by-one — users can create 6 reminders despite a limit of 5
Current code (
reminders.pyline 478):> 5only evaluates toTrueonce the user already has 6 reminders in the database — meaning the 6th is always written before the check can block it. The denial never fires on the 6th attempt.Fix:
Steps to reproduce (as a regular member in
#bot-commands):!remind listto confirm all 6 are savedExpected: the bot sends a denial on the 6th attempt and does not save it
Bug 2: Off-by-one — users can delete 6 reminders at once despite a limit of 5
The docstring explicitly states the limit is 5, but the same
>vs>=mistake is present:Current code (
reminders.pyline 660-663):> 5is onlyTruewhen 6 or more IDs are passed, so passing exactly 6 IDs is accepted and all 6 are deleted. The docstring and the behaviour directly contradict each other.Fix:
Steps to reproduce (as a regular member in
#bot-commands):!remind listand copy the 6 reminder IDsExpected: the bot sends a denial when more than 5 IDs are provided and deletes nothing
Bug 3: Reminders are silently deleted with no user notification when the bot loses channel cache
Current code (
reminders.pyline 248-258):self.bot.get_channel()only checks the local cache — it does not make a Discord API call. If the bot restarts or a channel temporarily falls out of cache during an outage,get_channel()returnsNoneeven though the channel still exists and is perfectly valid. The reminder is then permanently deleted from the database with no DM, no message, and no indication to the user that their reminder was lost. The content is gone with no way to recover it.Fix: use
fetch_channel()to fall back to the Discord API before giving up, and DM the user if the reminder truly cannot be delivered:Steps to reproduce:
!remind new 10m check thisExpected: the user receives a DM with their reminder content explaining it could not be delivered, so they can recreate it
oliveman-au commentedon May 5, 2026
I'd love to open a PR to fix all of the bugs outlined above if I'm able to be assigned. I'm happy to provide image proof reproducing each one if needed before any work begins. Thanks for your time!
[-]Off-by-one error allows regular users to create 6 reminders despite a limit of 5[/-][+]Multiple off-by-one errors and silent reminder deletion with no user notification in reminders.py[/+]wookie184 commentedon May 5, 2026
1 and 2 are simple and sound good.
For 3, generally all channels should be cached, so it isn't a massive issue exactly as described, but it is still an issue as the channel may simply not exist. This has an existing issue at #3015. I think DMing sounds sensible. People can also react to reminders to also be reminded, should they be DMed as well?
Also, we have a utility for fetching channel if not cached, https://bot-core.pythondiscord.com/output/pydis_core.utils.channel.html#pydis_core.utils.channel.get_or_fetch_channel, you can search the codebase for examples of how it's used.
Overall sounds good, I'll approve and assign you.
oliveman-au commentedon May 7, 2026
PR can be found here - #3509