Skip to content

[BUG] Race condition involving AutoDefer with time > 0s #1804

@mifuyutsuki

Description

@mifuyutsuki

Library Version

5.16.0rc3 @ a1540e9

Describe the Bug

When using an interaction command with AutoDefer set to greater than 0 seconds, it is possible for either it or the command callback to raise an error, due to either side thinking that the interaction hasn't already been deferred/responded to.

This manifests in multiple different ways depending on the timing of the error:

  • Message has been sent before AutoDefer starts: OK.
  • Message is being sent as AutoDefer starts: OK, but an HTTP 400/404 error is logged.
    • This is due AutoDefer trying to defer, as send() has sent a POST response, but has not yet set ctx.responded (and ctx.deferred).
  • Message is being sent as AutoDefer is running: Error message "HTTPException: Interaction has already been acknowledged."
    • This is due to send() sending an initial response POST rather than a followup PATCH, as AutoDefer has deferred, but has not yet set ctx.deferred.
  • Message has not yet been sent and AutoDefer is completed: OK.

Additionally, if the command callback calls defer() and AutoDefer has already deferred the message, either an "HTTPException: 400" or "AlreadyDeferred" error is emitted.

The underlying issue is that ctx.deferred and ctx.responded are set after the HTTP request(s), rather than before, which means things like AutoDefer may break.

Steps to Reproduce

The following steps may or may not be reproducible depending on the response time of Discord's HTTP endpoints. In my case, the HTTP request took me in the order of hundreds of ms from my computer, which made it easy for me to reproduce.

  1. Create a slash command with the following:
    1.1. AutoDefer (@auto_defer()) set to some seconds (not 0)
    1.2. Call to asyncio.sleep() for the same or close amount of time as auto_defer time
    1.3. Call to ctx.send() at the end to check if it works
  2. Run the bot and call the command
  3. Receive a message or an error traceback. Also check for ERROR logs.

Expected Results

Neither AutoDefer nor send() produce an error.

Minimal Reproducible Code

@slash_command(name="test")
@auto_defer(time_until_defer=1.0)
async def test_cmd(ctx: SlashContext):
  await asyncio.sleep(1.0)
  # Also can produce an error:
  # await ctx.defer()
  await ctx.send("Pong")

Traceback

AutoDefer after send() - No traceback, but error log:

[2025-12-21 23:31:49,462] [ERROR] interactions | POST::https://discord.com/api/v10/interactions/.../.../callback: 400        
[2025-12-21 23:31:49,468] [INFO] interactions | Command Called: test with event.ctx.args = [] | event.ctx.kwargs = {}

send() during AutoDefer - Traceback sent as message:

Traceback (most recent call last):
  File "...\interactions\client\client.py", line 2028, in __dispatch_interaction
    response = await callback
               ^^^^^^^^^^^^^^
  File "...\interactions\client\client.py", line 1896, in _run_slash_command
    return await command(ctx, **ctx.kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\interactions\models\internal\command.py", line 132, in __call__
    await self.call_callback(self.callback, context)
  File "...\interactions\models\internal\application_commands.py", line 877, in call_callback
    return await self.call_with_binding(callback, ctx, *new_args, **new_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\interactions\models\internal\callback.py", line 44, in call_with_binding
    return await callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\test.py", line 31, in test_cmd
    await ctx.send("Command called")
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\interactions\models\internal\context.py", line 586, in send
    return await super().send(
           ^^^^^^^^^^^^^^^^^^^
  File "...\interactions\client\mixins\send.py", line 124, in send
    message_data = await self._send_http_request(message_payload, files=files or file)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\interactions\models\internal\context.py", line 505, in _send_http_request
    message_data = await self.client.http.post_initial_response(payload, self.id, self.token, files=files)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\interactions\api\http\http_requests\interactions.py", line 150, in post_initial_response
    return await self.request(
           ^^^^^^^^^^^^^^^^^^^
  File "...\interactions\api\http\http_client.py", line 478, in request
    await self._raise_exception(response, route, result)
  File "...\interactions\api\http\http_client.py", line 499, in _raise_exception
    raise HTTPException(response, response_data=result, route=route)
interactions.client.errors.HTTPException: HTTPException: 400|Bad Request || Interaction has already been acknowledged.

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.
  • I have removed my token from display, if visible.
  • I have attempted to debug this myself, and I believe this issue is with the library

Additional Information

A pull request will be opened to fix this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions