최신 AI 에이전트 스킬 21선 3편, 버그를 잡는 순서와 파이썬·노드 붙잡기
2편에서 코드를 맡기고 검증을 받는 쪽을 봤다. 3편은 그다음이다. 뭔가 잘못됐을 때. 순서를 정하는 스킬 하나, 파이썬을 붙잡는 스킬 하나, 노드를 붙잡는 스킬 하나.
셋은 계층이 다르다. 첫 번째는 방법이고 나머지 둘은 도구다. 방법 없이 도구만 있으면 어디를 멈춰야 할지 모르고, 방법만 있으면 멈출 곳이 없다.
1. systematic-debugging (v1.1.0)
software-development 카테고리. obra/superpowers에서 가져왔다.
4-phase root cause debugging: understand bugs before fixing.
설명에 부제목이 붙어 있다. 고치기 전에 이해한다. 그게 전부다.
첫 줄부터 경고로 시작한다
Random fixes waste time and create new bugs. Quick patches mask underlying issues.
랜덤으로 고치면 시간만 낭비하고 새 버그가 생긴다. 급하게 붙인 패치는 진짜 문제를 덮는다. 이 문장이 맨 앞에 있는 게 이 스킬의 성격이다.
Core principle: ALWAYS find root cause before attempting fixes.
Symptom fixes are failure.
증상을 없애는 건 실패다. 이게 논리가 아니라 이 스킬이 실제로 막는 사고다. 에이전트가 가장 자주 하는 행동이 이거다. 에러 메시지를 보고 얼추 고쳐보겠다고 바로 손대는 거다.
Iron Law
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
철법이라고 적혀 있다. 1단계가 끝나기 전에는 수정 제안을 아예 하지 않는다. "일단 이거 바꿔보세요"가 나가는 순간 스킬이 무너진다. 그리고 한 줄이 덧붙는다.
Violating the letter of this process is violating the spirit of debugging.
2편의 TDD 스킬이랑 같은 문장이다. 규칙의 글자를 지키되 정신을 어기면 안 된다. 형식적으로 4단계를 거쳤는데 실제 원인은 못 찾고 그랬다, 하면 그건 4단계를 거친 게 아니라 글자만 쓴 거다.
Feedback Loop Rule
이게 이 스킬에서 제일 실용적인 부분이다. 코드를 읽고 원인을 추측하기 전에, 먼저 사용자가 본 증상을 빨갛게 만드는 좁고 빠른 명령을 만들어야 한다.
루프가 만족해야 할 조건이 네 개다.
1. 한 명령으로 정확한 증상이 재현된다
2. 이 버그에서만 실패하고, 고치면 통과한다
3. 반복 실행할 수 있을 만큼 빠르다
4. 결정적이다 — 매번 같은 결과
재현이 잘 안 되면 시간이 더 걸려도 루프를 만들어야 한다. 추측하지 말라고 명시돼 있다. 이게 이 스킬이 막으려는 실패 모드가 딱 이것이다. 루프 없이 원인을 "판단"해 버리는 거다.
루프를 만들 때 순서가 있다.
1. 버그에 닿는 이음매의 실패 테스트 (단위/통합/E2E)
2. 구동 중인 dev 서버에 대한 HTTP 스크립트나 curl
재현이 아예 안 되면 데이터를 더 모은다. 역시 추측 금지다.
반드시 써야 하는 순간
- 시간 압박 (추측이 tempting해지는 순간)
- "빠른 수정 하나면 될 것 같아"
- 이미 여러 수를 시도했고
- 이전 수리가 실패했고
- 문제를 완전히 이해 못하고 있을 때
건너뛰지 말아야 할 순간
- 이슈가 단순해 보일 때 (단순 버그도 근본 원인이 있음)
- 바쁠 때
- 지금 당장 고쳐달라는 압박이 있을 때
마지막 항목이 가장 현실적이다. 바쁠 때가 바로 이 스킬이 필요한 때다. 바쁘면 대충 고쳐서 넘기고 싶어지니까.
2. python-debugpy (v1.0.0)
software-development 카테고리.
Debug Python: pdb REPL + debugpy remote (DAP).
도구 세 개를 상황별로 나눠놓는다.
| 도구 | 언제 |
|---|---|
breakpoint() + pdb | 로컬, 인터랙티브, 가장 간단. 소스에 넣고 그냥 돌리면 그 줄에서 REPL |
python -m pdb | 소스 편집 없이 기존 스크립트를 pdb로 실행 |
debugpy | 원격·헤드리스, 이미 돌고 있는 프로세스에 attach. DAP로 말하고 터미널에서 스크립팅 가능, 게이트웨이·데몬 같은 장시간 프로세스에 필수 |
스킬이 지시를 한다.
Start with breakpoint(). It's the cheapest thing that works.
동작하는 것 중 가장 싼 걸로 시작하라. 여기서 "가장 싼"이 판단 기준이라는 게 중요하다. 복잡한 도구를 먼저 꺼내면 그 도구를 배우는 시간이 디버깅 시간에 낭비된다.
언제 쓰는지
- 함수 안을 한 줄씩 들어가면서 컬렉션이 변하는 걸 보고 싶을 때
- 게이트웨이나 tui_gateway 같은 장시간 프로세스가 이상 동작하고 재시작할 수 없을 때
- 프로덕션에서 터진 예외 지점의 로컬 변수를 보고 싶을 때
- 서브프로세스(_SlashWorker, PTY bridge worker)가 진짜 버그 지점일 때
뒤의 두 항목이 이 스킬의 존재 이유다. 헤르메스에서 실제로 겪은 문제의 대부분이 여기 있다. 부모 프로세스는 멀쩡한데 자식 프로세스가 이상 동작하는데 부모 로그에는 아무것도 안 찍힌다.
반대로 쓰지 말아야 할 때도 명시돼 있다.
Don't use for: print()/logging.debug로 1분 안에 해결되는 것,
pytest -vv --tb=long --showlocals가 이미 보여주는 것
pdb 명령
가장 자주 쓰는 것만 추리면 이렇다.
n 한 줄 건너뛰기 s 함수 안으로 들어가기 r 함수에서 나오기 c 계속
w 호출 스택 u / d 스택 위아래 a 현재 함수 인자 출력
p expr 값 출력 pp expr 예쁘게 출력 l / ll 주변 소스
b file:line 중단점 b file:line, cond 조건부 interact 현재 스코프 REPL로
interact가 이 스킬에서 제일 강력하다. 현재 스코프에서 임포트도 되고 복잡한 객체를 들여다보기도 되고 상태를 바꾸는 메서드 호출까지 된다. 단 로컬 변수는 기본이 읽기 전용이라 !x = 42처럼 바꿔야 한다. 이걸 몰랐을 때 "변경이 안 먹는다" 하고 헤맸던 적이 있다.
post-mortem
예외가 이미 터진 뒤에 그 지점을 보고 싶을 때다.
import pdb, sys
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])
스크립트 전체를 감싸는 방법도 있다.
python -m pdb -c continue script.py
-c continue가 핵심이다. 첫 줄에서 멈추지 말고 가다가, 터지는 순간 그 프레임에서 잡는다. REPL/주피터에서는 sys.excepthook 훅으로 전역에 걸어버릴 수도 있다.
debugpy를 쓸 때 주의
게이트웨이는 프로덕션이 아니라 별도 개발 체크아웃과 별도 데이터 홈에서 디버깅한다. 순서가 정해져 있다.
source ./activate
python -m pm.build_env --source . --out .venv --group dev --group test
.venv/bin/python -c "import debugpy; print(debugpy.__file__)"
dev 그룹에 들어있지 않은 게 문제다. all로 잡으면 debugpy가 안 들어온다. 그리고 출력이 이미 있으면 안 된다. 그 환경의 프로세스를 멈추고 지운 다음 다시 만든다. 프로덕션 환경에 debugpy를 추가하면 안 된다는 경고가 붙어 있는데 이건 사소한 게 아니라 운영 환경 오염이다.
3. node-inspect-debugger (v1.0.0)
같은 구조로 파이썬 스킬의 짝이다. node inspect가 기본이고, 자동화가 필요할 때 CDP를 쓴다.
Debug Node.js via --inspect + Chrome DevTools Protocol CLI.
도구 선택도 같은 우선순위를 강제한다.
Prefer node inspect first. It's always available and the REPL is fast.
언제 쓰는지
- ui-tui가 크래시하거나 이상 동작할 때 (pre-render 전 React/Ink 상태를 보려고)
- tui_gateway 자식 프로세스(_SlashWorker, PTY bridge)가 이상 동작할 때
- console.log로는 닿지 못하는 클로저 안의 값을 볼 때
- 실행 중 프로세스에 attach해서 CPU 프로파일이나 힙 스냅샷을 뜰 때
네 번째 항목이 은근히 중요하다. 클로저 안의 값은 console.log로 찍으려면 소스를 고치거나 우회해야 한다. 그건 소스 오염이다. 브레이크포인트면 소스를 건드리지 않는다.
실행 중인 프로세스 붙잡기
이게 이 스킬의 실전 핵심이다. 이미 돌고 있는 게이트웨이에 붙는 방법이다.
# 1. 실행 중인 프로세스에 SIGUSR1을 보내 인스펙터를 켠다
kill -SIGUSR1 <pid>
# Node가 출력한다: Debugger listening on ws://127.0.0.1:9229/<uuid>
# 2. 붙는다
node inspect -p <pid>
# 또는 URL로
node inspect ws://127.0.0.1:9229/<uuid>
재시작 없이 붙는 게 이게 필요한 이유다. 처음부터 인스펙터를 켜고 싶으면 이렇게.
node --inspect script.js # 127.0.0.1:9229에서 대기, 계속 실행
node --inspect-brk script.js # 대기 + 첫 줄에서 멈춤
node --inspect=0.0.0.0:9230 script.js # 호스트·포트 지정
타입스크립트는 tsx를 끼워야 한다.
node --inspect-brk --import tsx script.ts
# 구버전 tsx는
node --inspect-brk -r tsx/cjs script.ts
--inspect와 --inspect-brk 차이가 실전에서 헷갈린다. 멈추지 않고 기다리기만 할 거면 --inspect, 첫 줄에서 멈춰서 내부 상태를 보려면 --inspect-brk다. 브레이크포인트를 어디에 걸든 첫 줄부터 확인하고 싶으면 -brk가 편하다.
REPL 명령
프롬프트가 debug>다.
c / cont 계속 n / next 한 줄 s / step 들어가기 o / out 나오기
sb('file.js', 42) 소스 라인 브레이크포인트 sb(42) 현재 파일 라인 sb('함수명')
cb('file.js', 42) 브레이크포인트 해제 breakpoints 목록
bt 호출 스택 list(5) 주변 5줄 watch('expr') 매번 평가
repl 현재 스코프 REPL (Ctrl+C로 탈출) exec expr 한 번만 평가
restart / kill / .exit
repl 서브 모드가 핵심이다. 여기선 로컬이랑 클로저 변수에 바로 접근된다. 파이썬의 interact에 해당하는 거다. Ctrl+C로 debug>로 돌아온다.
브레이크포인트를 일일이 손으로 걸기 번거로우면 chrome-remote-interface로 CDP를 스크립팅한다. 에이전트 루프에서 자동으로 브레이크포인트를 여러 개 걸고 여러 실행에 걸쳐 상태를 모을 때는 이쪽이 맞다.
이번 편 정리
| 스킬 | 버전 | 하는 일 |
|---|---|---|
| systematic-debugging | 1.1.0 | 4단계로 근본 원인부터 |
| python-debugpy | 1.0.0 | pdb REPL, post-mortem, debugpy 원격 attach |
| node-inspect-debugger | 1.0.0 | node inspect REPL, SIGUSR1 attach, CDP 자동화 |
셋이 한 축을 이룬다. 순서를 정하고, 파이썬을 멈추고, 노드를 멈춘다. 순서가 없는 상태에서 브레이크포인트를 걸면 거기에선 아무것도 안 보인다.
앞으로는 4편에서 커밋 전에 코드를 점검하는 쪽으로 넘어간다.
AI Knowledge Hub