discord.py 자주 나는 에러 TOP 10 - 봇 개발 중 만나는 실전 오류 원인부터 해결까지 🚨
목차45개 항목
안녕하세요! Mojangcam입니다 😎
예전에 디스코드 봇 오류 해결 총정리(입문편)에서 인텐트·토큰 같은 첫날 에러 5가지를 정리했었죠. 그런데 봇을 조금 키우다 보면 그때는 만날 일도 없던 에러들이 우르르 쏟아져요. Cog로 코드를 쪼개고, DB를 붙이고, 버튼·모달까지 넣기 시작하면 딱 그렇습니다 😅
그래서 오늘은 입문 단계를 넘긴 뒤에 진짜 자주 만나는 에러 10가지를 증상 → 원인 → 해결 순서로 싹 모았어요. 에러 메시지를 그대로 Ctrl+F 해서 찾아 쓰라고 만든 글이니까, 즐겨찾기 해두고 사전처럼 쓰시면 됩니다!

📌 시작 전에 - 버전부터 맞추기
에러 해결의 절반은 버전 확인이에요. 이 글은 아래 환경 기준입니다.
- Python 3.10 이상 (아래 예제에 쓰는 python-dotenv 1.2.x가 3.10+, aiosqlite 0.22.x가 3.9+를 요구해요)
- discord.py 2.7.1 (2026-09 기준 PyPI 최신)
- aiosqlite 0.22.1, python-dotenv 1.2.x (DB·환경변수 예제용)
python -m pip install -U discord.py aiosqlite python-dotenv
python -m pip show discord.pypip이 아니라 python -m pip으로 설치하는 게 포인트예요. 파이썬이 여러 개 깔린 PC에서 '실행하는 파이썬'과 '설치되는 파이썬'이 어긋나는 사고를 절반 이상 막아줍니다!
📌 1. ModuleNotFoundError: No module named 'aiosqlite'
증상
ModuleNotFoundError: No module named 'aiosqlite'원인
sqlite3는 파이썬 기본 내장이라 설치가 필요 없지만, aiosqlite는 별도 설치 패키지예요. 봇에 DB를 붙이는 글을 따라 하다가 이 에러를 만나는 분이 제일 많습니다. 설치를 했는데도 난다면 가상환경(venv)을 켜기 전에 설치했거나, 다른 파이썬에 깔린 경우예요.
해결
python -m pip install -U aiosqlite
# 윈도우에서 py 런처를 쓴다면
py -3 -m pip install -U aiosqlite
# 어디에 깔렸는지 확인
python -m pip show aiosqlite하나씩 뜯어볼게요 💡
- python -m pip : 지금 실행 중인 파이썬에 정확히 설치해요. 경로 꼬임 방지 1순위!
- pip show :
Location:줄에 나온 경로가 내가 쓰는 파이썬의 site-packages인지 보세요. - 가상환경을 쓴다면
.venv\Scripts\activate(윈도우) /source .venv/bin/activate(맥·리눅스)를 먼저 하고 설치해야 해요.
💬 참고로 이 에러는 패키지 이름만 바꿔서 그대로 통합니다.
No module named 'dotenv'→pip install python-dotenv,No module named 'aiohttp'→pip install aiohttp식으로요!
📌 2. ExtensionNotFound: Extension 'cogs.music' could not be loaded or found.
증상
discord.ext.commands.errors.ExtensionNotFound: Extension 'cogs.music' could not be loaded or found.원인
Cog를 불러올 때 쓰는 경로는 파일 경로가 아니라 파이썬 모듈 경로예요. 그리고 의외로 흔한 원인이 실행 위치입니다. cogs 폴더가 있는 곳이 아닌 다른 폴더에서 python bot.py를 돌리면 파이썬이 cogs를 못 봐요.
💬 참고로 표기법에 따라 에러 이름이 달라져요. 표기는 맞는데 파일이 없으면
ExtensionNotFound가 나고, 슬래시(/)나.py를 붙이면ModuleNotFoundError: No module named 'cogs/music'쪽이 납니다. 둘 다 원인은 같으니 아래 방법으로 함께 해결돼요!
해결
# discord.py 2.7.x / Python 3.10+
# ❌ 틀린 예
# await bot.load_extension("cogs/music.py")
# await bot.load_extension("cogs.music.py")
# ✅ 맞는 예 - 점(.)으로 구분, 확장자 없음
await bot.load_extension("cogs.music")체크할 것 3가지예요.
- 경로 표기 :
폴더.파일이름, 확장자.py는 빼기 - 실행 위치 :
bot.py가 있는 폴더에서 실행하기 (cd 프로젝트폴더후python bot.py) - 오타 : 폴더명이
cog인데cogs로 썼거나, 파일명 대소문자가 다른 경우 (리눅스는 대소문자를 구분해요!)
💡 폴더 전체를 자동으로 불러오면 오타 사고를 줄일 수 있어요.
import os
async def load_all_cogs(bot):
for filename in os.listdir("./cogs"):
if filename.endswith(".py") and not filename.startswith("_"):
await bot.load_extension(f"cogs.{filename[:-3]}")📌 3. TypeError: object NoneType can't be used in 'await' expression (Cog 로딩)
증상
Cog 파일은 분명 있는데, 불러오는 순간 이 에러가 떠요. 트레이스백 맨 아래 두 줄이 이렇게 짝을 이룹니다.
TypeError: object NoneType can't be used in 'await' expression
The above exception was the direct cause of the following exception:
...
discord.ext.commands.errors.ExtensionFailed: Extension 'cogs.music' raised an error: TypeError: object NoneType can't be used in 'await' expression원인
discord.py 1.x 시절 문법을 2.x에서 쓴 것이 원인이에요. 2.x부터는 확장 로딩이 비동기로 바뀌어서 setup 함수도 async여야 하고, add_cog도 await해야 합니다. 구버전 강좌를 따라 하면 100% 만나는 에러예요.
해결
# discord.py 2.7.x / Python 3.10+ - cogs/music.py
import discord
from discord.ext import commands
class Music(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command()
async def ping(self, ctx):
await ctx.send("퐁! 🏓")
# ❌ 1.x 방식: def setup(bot): bot.add_cog(Music(bot))
# ✅ 2.x 방식
async def setup(bot: commands.Bot):
await bot.add_cog(Music(bot))하나씩 뜯어볼게요 💡
- async def setup : 2.x는
setup을await로 호출해요. 일반 함수면None을 await하게 돼서 위 에러가 납니다. - await bot.add_cog(...) : 2.x에서 코루틴으로 바뀌었어요.
await를 빼면 다음에 나올 9번 경고(coroutine 'BotBase.add_cog' was never awaited)가 함께 뜹니다. - 마찬가지로
bot.load_extension도await bot.load_extension(...)으로 써야 하고, 봇 실행 전에 부르려면setup_hook안에서 호출해야 해요.
📌 4. NotFound: 404 (error code: 10062): Unknown interaction
증상
슬래시 명령어를 실행하면 디스코드에는 "애플리케이션이 응답하지 않습니다"가 뜨고, 터미널에는 이 에러가 나요.
discord.errors.NotFound: 404 Not Found (error code: 10062): Unknown interaction원인
디스코드는 상호작용(Interaction)에 3초 안에 첫 응답을 요구해요. AI 호출, 크롤링, 무거운 DB 조회처럼 오래 걸리는 작업을 먼저 하고 나중에 send하면 이미 토큰이 만료된 뒤라 404가 납니다.
해결
오래 걸릴 것 같으면 먼저 defer로 "생각 중..."을 띄우고, 결과는 followup으로 보내세요.
# discord.py 2.7.x / Python 3.10+
import asyncio
@bot.tree.command(name="검색", description="오래 걸리는 작업 예시")
async def search(interaction: discord.Interaction, keyword: str):
await interaction.response.defer(thinking=True) # 3초 룰 방어!
await asyncio.sleep(5) # 실제로는 API 호출·DB 조회 등
await interaction.followup.send(f"**{keyword}** 검색 완료!")하나씩 뜯어볼게요 💡
- defer(thinking=True) : 응답 시간을 15분까지 늘려줘요. 사용자에게는 로딩 표시가 보입니다.
- defer(ephemeral=True) : 나만 보이게 하고 싶으면 여기서 지정해야 해요. followup에서 바꿔도 안 먹힙니다!
- followup.send : defer 이후의 모든 메시지는
response가 아니라followup으로 보내요.
💬 명령어 안에
time.sleep()이 있으면 봇 전체가 멈춰서 이 에러가 연쇄로 터져요. 비동기 코드에서는 꼭 asyncio.sleep()을 쓰세요!
📌 5. InteractionResponded: This interaction has already been responded to before
증상
discord.errors.InteractionResponded: This interaction has already been responded to before원인
하나의 상호작용에 interaction.response.___를 두 번 이상 쓴 거예요. send_message 후에 또 send_message를 했거나, defer 뒤에 send_message를 부른 경우죠.
해결
첫 응답은 response, 두 번째부터는 무조건 followup이에요.
# discord.py 2.7.x / Python 3.10+
@bot.tree.command(name="단계", description="응답을 여러 번 보내는 예시")
async def step(interaction: discord.Interaction):
await interaction.response.send_message("1단계 시작!") # 첫 응답
await interaction.followup.send("2단계 진행 중...") # 두 번째부터
await interaction.followup.send("3단계 완료 ✅")💡 이미 응답했는지 헷갈리는 구조라면 interaction.response.is_done()으로 분기하면 안전해요.
if interaction.response.is_done():
await interaction.followup.send("결과입니다!")
else:
await interaction.response.send_message("결과입니다!")📌 6. 슬래시 명령어가 목록에 안 보여요 (에러 없음)
증상
봇은 온라인이고 에러도 없는데, 디스코드에서 /를 쳐도 내 명령어가 안 뜹니다. 제일 답답한 케이스죠 😭
원인
bot.tree.sync()를 아예 안 했다- 했는데 글로벌 동기화라서 디스코드에 반영되기까지 최대 1시간이 걸린다
- 봇 초대 링크에
applications.commands스코프가 빠져 있다
해결
개발 중에는 길드(서버) 단위 동기화를 쓰세요. 즉시 반영됩니다!
# discord.py 2.7.x / Python 3.10+
GUILD_ID = discord.Object(id=123456789012345678) # 테스트 서버 ID
class MyBot(commands.Bot):
async def setup_hook(self):
await self.load_extension("cogs.music")
self.tree.copy_global_to(guild=GUILD_ID)
await self.tree.sync(guild=GUILD_ID) # 개발용: 즉시 반영
# await self.tree.sync() # 배포용: 전 서버, 최대 1시간하나씩 뜯어볼게요 💡
- setup_hook : 봇이 로그인한 직후, 이벤트를 받기 전에 한 번 실행돼요. Cog 로딩·동기화의 정석 위치입니다.
- copy_global_to(guild=...) : 전역으로 등록한 명령어를 테스트 서버로 복사해요. 이게 없으면 길드 sync에 아무것도 안 올라갑니다.
- 초대 링크는 bot + applications.commands 스코프 둘 다 체크해서 다시 만들어 주세요.
- 그래도 안 보이면 디스코드 클라이언트를 Ctrl+R로 새로고침!
📌 7. Forbidden: 403 (error code: 50013): Missing Permissions
증상
discord.errors.Forbidden: 403 Forbidden (error code: 50013): Missing Permissions원인
봇에게 그 동작을 할 권한이 없어요. 그런데 "관리자 권한 줬는데요?" 하는 경우가 많은데, 디스코드는 역할 순서도 함께 봅니다. 봇 역할이 대상 유저 역할보다 아래에 있으면 강퇴·역할 부여가 막혀요.
해결
- 서버 설정 → 역할에서 봇 역할을 대상 역할보다 위로 드래그
- 채널별 권한 오버라이드 확인 (서버 권한이 있어도 채널에서 막힐 수 있어요)
- 코드에서 미리 확인하고 친절하게 안내하기
# discord.py 2.7.x / Python 3.10+
@bot.tree.command(name="청소", description="메시지 삭제")
async def clear(interaction: discord.Interaction, count: int):
perms = interaction.channel.permissions_for(interaction.guild.me)
if not perms.manage_messages:
await interaction.response.send_message(
"❌ 봇에게 **메시지 관리** 권한이 없어요!", ephemeral=True
)
return
await interaction.response.defer(ephemeral=True)
deleted = await interaction.channel.purge(limit=count)
await interaction.followup.send(f"{len(deleted)}개 삭제 완료 🧹")💬 guild.me가 서버 안에서의 '봇 자신'이에요.
permissions_for로 채널 기준 실제 권한을 볼 수 있습니다.

📌 8. HTTPException: 400 (error code: 50035): Invalid Form Body
증상
임베드나 버튼을 보낼 때 이런 에러가 나요.
discord.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In embeds.0.description: Must be 4096 or fewer in length.원인
디스코드의 길이·개수 제한을 넘긴 거예요. 로그나 검색 결과를 그대로 임베드에 밀어 넣을 때 자주 터집니다.
해결
자주 걸리는 제한부터 외워두세요.
| 항목 | 제한 |
|---|---|
| 임베드 description | 4,096자 |
| 임베드 title / field name | 각 256자 |
| 임베드 field value | 1,024자 |
| 임베드 필드 개수 | 25개 |
| 임베드 전체 합계 | 6,000자 |
| 메시지 본문 | 2,000자 |
| 버튼 라벨 | 80자 |
| View 구성 | 한 줄 버튼 5개, 최대 5줄 |
# discord.py 2.7.x / Python 3.10+
MAX_DESC = 4096
def safe_embed(title: str, body: str) -> discord.Embed:
if len(body) > MAX_DESC:
body = body[: MAX_DESC - 3] + "..." # 잘라내기
return discord.Embed(title=title[:256], description=body)💡 에러 메시지 아래에 In embeds.0.description:처럼 어디가 문제인지 정확히 찍혀서 나와요. 그 줄만 읽으면 바로 해결됩니다!
📌 9. RuntimeWarning: coroutine '...' was never awaited
증상
에러로 멈추지는 않는데, 코드가 조용히 아무 일도 안 해요.
RuntimeWarning: coroutine 'Messageable.send' was never awaited원인
await를 빼먹은 거예요. discord.py의 함수는 대부분 코루틴이라, await 없이 부르면 실행되지 않고 코루틴 객체만 만들어지고 끝납니다. 2.x로 넘어오면서 add_cog, load_extension, setup_hook처럼 예전엔 동기였던 것들이 비동기로 바뀌어서 더 자주 만나게 됐어요.
해결
# ❌ ctx.send("안녕!")
# ✅
await ctx.send("안녕!")
# ❌ bot.add_cog(Music(bot))
# ✅
await bot.add_cog(Music(bot))💡 async def 안이 아니면 await를 못 써요. 일반 함수에서 코루틴을 부르고 싶다면 그 함수를 async def로 바꾸는 게 정석입니다.
📌 10. sqlite3.OperationalError: database is locked
증상
봇이 잘 돌다가 사용자가 몰리면 갑자기 이 에러가 나요.
sqlite3.OperationalError: database is locked원인
SQLite는 쓰기 작업을 한 번에 하나만 허용해요. 명령어마다 새 커넥션을 열거나, commit() 없이 커넥션을 붙잡고 있으면 서로 잠금을 기다리다 타임아웃이 납니다.
해결
커넥션을 하나만 만들어 재사용하고, 쓰기 후에는 반드시 commit() 하세요. WAL 모드를 켜면 읽기·쓰기 충돌도 크게 줄어요.
# discord.py 2.7.x / aiosqlite 0.22.x / Python 3.10+
import aiosqlite
class MyBot(commands.Bot):
async def setup_hook(self):
self.db = await aiosqlite.connect("bot.db")
await self.db.execute("PRAGMA journal_mode=WAL") # 동시성 개선
await self.db.execute(
"CREATE TABLE IF NOT EXISTS points (user_id INTEGER PRIMARY KEY, point INTEGER)"
)
await self.db.commit()
async def close(self):
await self.db.close() # 종료 시 정리
await super().close()하나씩 뜯어볼게요 💡
- journal_mode=WAL : 읽는 중에도 쓸 수 있게 해줘요. 봇처럼 짧은 쿼리가 많은 환경에 딱입니다.
- 커넥션 1개 재사용 :
self.db에 보관해 두고 Cog에서self.bot.db로 꺼내 쓰세요. - commit 필수 :
INSERT·UPDATE후await self.bot.db.commit()을 빼면 데이터가 안 남고 잠금만 남아요. - 급할 땐
aiosqlite.connect("bot.db", timeout=10)으로 대기 시간을 늘려 임시 방어할 수 있어요.
📌 에러 안 나는 기본 골격 (복붙 OK)
위 10가지를 전부 피해 가는 2.x 표준 뼈대예요. 새 봇을 시작할 때 이걸로 출발하세요!
# discord.py 2.7.1 / aiosqlite 0.22.x / Python 3.10+
# 파일명은 bot.py 등으로! (discord.py 금지)
import os
import aiosqlite
import discord
from discord.ext import commands
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN") # .env 에 DISCORD_TOKEN=...
GUILD_ID = os.getenv("TEST_GUILD_ID") # .env 에 TEST_GUILD_ID=...
intents = discord.Intents.default()
intents.message_content = True # 포털 스위치도 함께 ON!
class MyBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix="!", intents=intents)
async def setup_hook(self):
# 1) DB 준비
self.db = await aiosqlite.connect("bot.db")
await self.db.execute("PRAGMA journal_mode=WAL")
await self.db.commit()
# 2) Cog 자동 로딩
if os.path.isdir("./cogs"):
for filename in os.listdir("./cogs"):
if filename.endswith(".py") and not filename.startswith("_"):
await self.load_extension(f"cogs.{filename[:-3]}")
# 3) 슬래시 명령어 동기화
if GUILD_ID:
guild = discord.Object(id=int(GUILD_ID))
self.tree.copy_global_to(guild=guild)
await self.tree.sync(guild=guild) # 개발: 즉시 반영
else:
await self.tree.sync() # 배포: 전 서버
async def close(self):
await self.db.close()
await super().close()
async def on_ready(self):
print(f"{self.user} 로그인 완료!")
bot = MyBot()
@bot.tree.command(name="핑", description="봇 응답 속도를 확인해요")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message(
f"퐁! 🏓 `{round(bot.latency * 1000)}ms`"
)
bot.run(TOKEN).env 파일은 bot.py와 같은 폴더에 이렇게 만들어 주세요.
DISCORD_TOKEN=여기에_봇_토큰_붙여넣기
TEST_GUILD_ID=여기에_테스트_서버_ID💡 그리고 .gitignore에 .env를 꼭 넣어주세요! 토큰이 들어간 코드를 깃허브에 올리면 디스코드가 자동으로 감지해서 토큰을 무효화해 버립니다.
.env
__pycache__/
*.db
📌 에러 잡는 순서 - 이 흐름만 기억하세요
- 에러 이름부터 읽기 (
NotFound?Forbidden?TypeError?) - 괄호 안 error code 확인 (10062·50013·50035는 위에서 다뤘어요)
- 트레이스백 맨 아래에서 두 번째 줄 — 내 코드의 어느 줄인지 알려줘요
python -m pip show discord.py로 버전 확인 (1.x 코드 섞임 의심)- 그래도 모르겠으면 에러 메시지를 그대로 검색하기
💬 그리고 봇이 커졌다면 에러 핸들러와 로깅을 붙이는 게 답이에요. 이건 디스코드 봇 만들기 #25 - 에러 핸들링·로깅 체계 잡기에서 통째로 다뤘으니 같이 보시면 좋아요!
📌 마무리
오늘은 discord.py로 봇을 키우는 과정에서 실제로 자주 만나는 에러 10가지를 정리해봤어요.
- No module named 'aiosqlite' →
python -m pip install aiosqlite - ExtensionNotFound → 점(.) 경로 + 실행 위치 확인
- NoneType can't be awaited →
async def setup+await add_cog - 10062 Unknown interaction → 3초 룰,
defer()먼저 - InteractionResponded → 두 번째부터는
followup - 슬래시 안 보임 → 길드 sync +
copy_global_to - 50013 Missing Permissions → 역할 순서·채널 권한
- 50035 Invalid Form Body → 임베드 길이·개수 제한
- never awaited 경고 →
await누락 - database is locked → 커넥션 1개 재사용 + WAL + commit
에러는 여러분이 뭔가 제대로 만들고 있다는 증거예요 🙌 막히는 에러가 있으면 메시지를 그대로 댓글에 남겨주세요. 다음 편에서는 디스코드 봇 만들기 #28로 시리즈를 이어갈 예정이니 기대해주세요!
📚 함께 보면 좋은 글
- 디스코드 봇 오류 해결 총정리 - 입문편 (intents·token 에러)
- discord.py 자주 나는 에러 TOP 10 - 실전편 ← 지금 이 글!
- 디스코드 봇 만들기 #25 - 에러 핸들링·로깅 체계 잡기
- 디스코드 봇 만들기 #27 - 티켓 담당자 배정·우선순위
