Discord bot setup_hook vs client.loop.create_task issue

Discord Bot Task Creation Problem

I’m building a Discord bot and need help with running background tasks. I want to create a function that tracks server activity and writes the data to a file every few seconds.

The issue happens when I try to start the background task. I get an AttributeError saying the loop attribute can’t be accessed in non-async contexts.

import os
import time
import asyncio
import discord
from discord.ext import commands, tasks

BOT_TOKEN = 'bot_token_here'
SERVER_NAME = "my_server"

msg_count = member_joins = 0
intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)

async def track_activity():
    await bot.wait_until_ready()
    global msg_count, member_joins

    while not bot.is_closed():
        try:
            with open("activity_log.txt", "a") as file:
                file.write(f"Timestamp: {int(time.time())}, Messages: {msg_count}, New Members: {member_joins}\n")

            msg_count = 0
            member_joins = 0

            await asyncio.sleep(10)

        except Exception as error:
            print(error)
            await asyncio.sleep(10)

@tasks.loop(seconds=10)
async def send_welcome(text_channel: discord.TextChannel):
    await text_channel.send("Hello everyone!")

@bot.event
async def on_ready():
    if not send_welcome.is_running():
        welcome_channel = await bot.fetch_channel("channel_id_here")
        send_welcome.start(welcome_channel)
    
    for server in bot.guilds:
        if server.name == SERVER_NAME:
            break
    
    print(f'{bot.user} connected to: {server.name} (ID: {server.id})')
    
    await bot.change_presence(
        activity=discord.Activity(name='monitoring', type=discord.ActivityType.watching)
    )

@bot.event
async def on_member_join(new_member):
    global member_joins
    member_joins += 1
    for ch in new_member.guild.channels:
        if str(ch) in ['general', 'welcome']:
            await ch.send(f"Welcome {new_member.mention}!")

@bot.event
async def on_message(msg):
    global msg_count
    msg_count += 1
    
    server_obj = bot.get_guild(12345678)
    allowed_channels = ["general", "commands", "chat"]
    authorized_users = ["admin_username"]

    if str(msg.channel) in allowed_channels and str(msg.author) in authorized_users:
        if msg.content == "!greet":
            await msg.channel.send("Hello there!")
        elif msg.content == "!count":
            await msg.channel.send(f"Total members: {server_obj.member_count}")

bot.loop.create_task(track_activity())
bot.run(BOT_TOKEN)

Error message:

AttributeError: loop attribute cannot be accessed in non-async contexts. Consider using either an asynchronous main function and passing it to asyncio.run or using asynchronous initialisation hooks such as Client.setup_hook

What’s the proper way to start background tasks in Discord.py? Should I use setup_hook instead?

You’re getting this error because you’re trying to access the event loop before the bot initializes it. Move your task creation to setup_hook - it runs during the bot’s async initialization, so the loop will be ready.

Replace that bot.loop.create_task(track_activity()) line with this method in your bot class:

async def setup_hook(self):
    self.bg_task = self.loop.create_task(track_activity())

But honestly? I’d refactor your track_activity function to use the @tasks.loop decorator like your send_welcome function does. You get better error handling and automatic restarts if something breaks. Just start it in setup_hook with your_task.start() - way cleaner than creating tasks manually.

yeah, use setup_hook - that’s your fix. you’re creating the task before discord.py sets up its event loop. just add async def setup_hook(self): self.create_task(track_activity()) to your bot class and ditch that bot.loop line at the bottom.

You’re getting that error because bot.loop.create_task() runs at module level, but there’s no event loop yet. The bot creates its loop during connection.

setup_hook works, but there’s an easier way. You’re already using @tasks.loop for send_welcome - just convert track_activity to the same pattern and start it in on_ready with your other task. Keeps everything consistent and you don’t need manual task creation.

Or if you want to keep it as a regular coroutine, just create the task inside on_ready where the loop definitely exists. Add bot.create_task(track_activity()) right after your presence change line. Either way fixes the timing issue.

This topic was automatically closed 4 days after the last reply. New replies are no longer allowed.