어제 고친 버그를 오늘 또 만드는 AI
결론부터 말하면, AI는 똑똑하지만 기억력이 없다. 그래서 삽질에서 얻은 교훈은 사람이 아니라 파일에 남겨야 한다.
1년 전 Claude Code로 개발에 복귀한 뒤 CodeIgniter 4 기반 솔루션 7개를 혼자 만들었다. 속도는 확실히 빨라졌다. 그런데 이상한 반복이 있었다. 어제 한 시간 걸려 잡은 문제를, 새 세션의 Claude가 오늘 똑같이 다시 만든다.
세션이 바뀌면 대화 맥락은 사라진다. 사람 동료라면 "아, 그거 지난번에 데였지" 하고 피해 갈 일을, AI는 매번 처음 보는 얼굴로 다시 밟는다. 지난 글에서 CLAUDE.md로 팀 규칙을 가르치는 법을 다뤘다면, 이번 글은 그 반대편 이야기다. 규칙이 아니라 흉터, 즉 삽질 교훈을 세션 간에 이어 붙이는 법이다.
이 글에서 예로 드는 교훈은 두 가지다.
.env값은 따옴표로 감싼다error_log()출력은 믿지 않는다
둘 다 사소해 보이지만, 모를 때는 반나절을 날리고 알고 나면 한 줄로 끝나는 종류의 지식이다. 메모리 파일이 가장 큰 효과를 내는 게 바로 이런 지식이다.
규칙은 CLAUDE.md, 흉터는 MEMORY.md
Claude Code가 세션을 넘어 기억하는 통로는 크게 두 가지다. 내가 쓰는 CLAUDE.md, 그리고 Claude가 스스로 쓰는 auto memory다.
|
구분 |
CLAUDE.md |
auto memory (MEMORY.md) |
|---|---|---|
|
누가 쓰나 |
사람 |
Claude (사람이 지시·수정 가능) |
|
위치 |
프로젝트 루트 |
|
|
공유 |
저장소에 커밋, 팀과 공유 |
내 머신에만 존재 |
|
로딩 |
세션 시작 시 전체 |
세션 시작 시 MEMORY.md 앞부분(약 200줄), 세부 파일은 필요할 때 |
|
담을 것 |
아키텍처, 코딩 규약, 금지 구역 |
실제로 겪은 함정, 교정받은 습관, 환경 특이사항 |
공식 문서도 이 둘을 같은 맥락에서 구분한다. CLAUDE.md는 Claude의 행동을 이끌기 위한 것이고, auto memory는 내가 고쳐준 내용을 Claude가 따로 수고 없이 배우게 하는 장치다. (Claude Code 공식 문서 - memory)
구조는 단순하다. MEMORY.md는 한 줄짜리 포인터를 모아둔 목차이고, 자세한 내용은 debugging.md, env.md 같은 주제별 파일로 나뉜다. 매 세션에는 목차만 올라오고, 주제 파일은 관련 작업을 할 때 열린다. 컨텍스트를 아끼면서도 "이런 함정이 있었다"는 존재 자체는 항상 알고 있게 되는 구조다.
~/.claude/projects/<프로젝트>/memory/
├── MEMORY.md # 목차, 매 세션 로딩
├── env.md # .env 관련 함정
└── debugging.md # 로그·디버깅 관련 함정
내 기준으로 정리하면 이렇다. "이렇게 짜라"는 CLAUDE.md, "여기서 데었다"는 MEMORY.md. 규칙은 팀과 함께 지키는 것이고, 흉터는 내 환경에서 실제로 난 상처라 남과 공유할 필요가 없는 경우가 많다.
교훈 1: .env 값은 따옴표로 감싼다
결론: CI4 .env의 문자열 값은 무조건 큰따옴표로 감싸게 했고, 그 뒤로 이 종류의 버그는 다시 나지 않았다.
CI4 프로젝트를 여러 개 세팅하다 보면 DB 비밀번호, API 키, 앱 이름 같은 값을 .env에 계속 넣게 된다. 문제는 따옴표 없이 쓴 값이 대부분은 멀쩡히 동작한다는 점이다. 그래서 Claude도, 나도 습관처럼 따옴표를 생략한다. 그러다 특정 값에서만 터진다.
# 겉보기엔 멀쩡하지만
database.default.password = p@ss#2026
app.name = AIvance Admin
# 이렇게 써야 안전하다
database.default.password = "p@ss#2026"
app.name = "AIvance Admin"
따옴표가 없으면 CI4의 DotEnv 파서는 값을 이렇게 처리한다.
- 공백이 있는 값: 따옴표로 감싸라는 예외를 던지며 부팅 자체가 실패한다. 차라리 이건 낫다. 바로 보이니까.
#이 들어간 값:#뒤를 주석으로 보고 잘라낼 수 있다. 비밀번호가 조용히 짧아지고, 증상은 "DB 접속 거부"로만 나타난다. 이게 진짜 무섭다.$가 들어간 값:${변수}형태는 다른 환경변수로 치환된다. 의도치 않은 값이 들어갈 여지가 생긴다.
특수문자가 섞인 비밀번호는 셸에서도, SQL에서도, .env에서도 각각 다른 방식으로 사람을 괴롭힌다. 이 함정은 한 번 겪고 나면 원인이 뻔한데, 겪기 전에는 "비밀번호를 잘못 넣었나"부터 의심하게 된다.
메모리에 남긴 형태
# env.md
## .env 값은 항상 큰따옴표
- 규칙: CI4 .env의 문자열 값은 "…"로 감싼다. 숫자·불리언만 예외.
- 이유: 따옴표 없으면 공백은 부팅 실패, `#`은 뒤가 주석으로 잘림,
`${}`는 치환됨. # 잘림은 에러 없이 "DB 접속 거부"로만 보인다.
- 확인법: php spark env 대신 env('키')를 직접 찍어 길이까지 비교.
- 출처: 2026 AIvance 서버 세팅 중 DB 비밀번호에서 발생.
핵심은 규칙, 이유, 증상, 확인법을 함께 적는 것이다. 규칙만 적으면 Claude는 "이 값은 특수문자가 없으니 괜찮겠지" 하고 예외를 만든다. 증상까지 적어두면, 나중에 "DB 접속 거부"를 만났을 때 Claude가 먼저 .env를 의심한다.
교훈 2: error_log()는 믿지 않는다
결론: "로그가 안 찍혔다"는 "코드가 실행되지 않았다"는 증거가 아니다. 이걸 메모리에 박아둔 뒤로 Claude의 헛다리 추론이 확 줄었다.
Claude Code에게 디버깅을 맡기면 흔히 이런 흐름이 나온다.
- 의심 지점에
error_log('여기 도착')을 넣는다 - 요청을 보낸다
- 로그 파일을 열어보니 아무것도 없다
- "이 코드는 실행되지 않습니다. 라우팅 문제로 보입니다"라고 결론 내린다
- 멀쩡한 라우트를 고치기 시작한다
4번이 함정이다. error_log()의 출력은 실행 경로마다 다른 곳으로 간다.
|
실행 환경 |
|
|---|---|
|
Apache + mod_php |
Apache 에러 로그 (vhost별로 다를 수 있음) |
|
PHP-FPM |
FPM 풀 설정에 따라 FPM 로그 또는 버려짐 |
|
|
터미널 stderr |
|
CI4 |
|
로컬은 macOS + Apache, 서버는 Ubuntu, 테스트는 CLI로 돌리다 보니 같은 코드의 로그가 세 군데로 흩어졌다. Claude는 자기가 연 파일 하나만 보고 "없다"고 판단한다. 거기에 writable/logs 쓰기 권한 문제나 production 환경의 로그 임계값까지 겹치면, 로그 부재는 거의 아무것도 증명하지 못한다.
메모리에 남긴 형태
# debugging.md
## error_log 출력 부재로 "미실행" 판단 금지
- 규칙: 로그가 없다는 이유만으로 코드가 실행 안 됐다고 결론 내리지 않는다.
- 이유: error_log는 Apache/FPM/CLI마다 출력 위치가 다르고,
log_message는 threshold와 writable 권한에 걸린다.
- 대신: 실행 여부는 응답에 임시 헤더를 박거나, 테스트에서 단언하거나,
file_put_contents로 절대경로 파일에 직접 기록해 확인한다.
- 디버깅 후 임시 코드는 반드시 제거.
여기서 중요한 건 "하지 마"가 아니라 "대신 이렇게"까지 적는 것이다. 금지만 적으면 Claude는 다른 그럴듯한 추측으로 같은 실수를 반복한다. 대안 확인법을 함께 주면, 다음 세션의 Claude는 로그가 비었을 때 결론 대신 확인 절차를 먼저 꺼낸다.
좋은 메모리 항목의 조건
두 교훈을 정리하면서 메모리 항목에도 품질 차이가 있다는 걸 알게 됐다. 쓸모 있는 항목은 다음 네 가지를 갖췄다.
- 한 줄 규칙 — 무엇을 하고, 무엇을 하지 않는지
- 이유 — 왜 그런지. 이유가 없으면 Claude는 "이번엔 예외"를 만든다
- 증상 — 이 함정에 빠졌을 때 겉으로 보이는 모습. 나중에 역추적의 단서가 된다
- 대안 또는 확인법 — 금지만 있으면 다른 실수로 우회한다
반대로 남기지 않는 게 나은 것도 있다.
- 코드나 git 이력을 보면 바로 알 수 있는 사실
- 이미 CLAUDE.md에 있는 규칙의 중복
- 이번 작업에만 해당하는 임시 상태 ("지금 auth 브랜치 작업 중")
- 감정이 섞인 기록 ("이거 진짜 짜증났음") — 정보가 없다
어디에 둘지 판단 기준
|
질문 |
예 → |
아니오 → |
|---|---|---|
|
팀 전체가 지켜야 하나? |
CLAUDE.md |
다음 질문 |
|
모든 프로젝트에 공통인가? |
|
다음 질문 |
|
이 프로젝트에서 실제로 겪은 함정인가? |
auto memory |
남기지 않는다 |
|
매번 기계적으로 막아야 하나? |
훅(hook)으로 강제 |
메모리로 충분 |
마지막 줄이 중요하다. .env 따옴표 규칙처럼 검사 가능한 것은 결국 PreToolUse 훅으로 옮기는 게 맞다. 메모리는 부탁이고 훅은 강제다. 메모리로 시작해서, 같은 실수가 두 번 나면 훅으로 승격시키는 흐름이 내 환경에서는 잘 맞았다.
운영하면서 정한 습관
- 삽질이 끝나면 바로 말한다. 문제를 해결한 직후 "방금 원인을 메모리에 남겨줘. 규칙, 이유, 증상, 확인법 형식으로"라고 한 줄 지시한다. 시간이 지나면 이유를 잊는다.
/memory로 주기적으로 열어본다. Claude가 스스로 적은 항목 중에는 과하게 일반화된 것도 있다. 한 달에 한 번 정도 훑어 틀린 건 고치고 낡은 건 지운다.- MEMORY.md는 목차로만 쓴다. 세션마다 앞부분만 올라오므로 상세 내용은 주제 파일로 빼고 목차는 한 줄씩 짧게 유지한다.
- 두 번 나면 승격한다. 메모리에 있는데도 같은 실수가 반복되면 훅이나 테스트로 옮긴다.
- 비밀값은 절대 적지 않는다. "비밀번호에
#이 있었다"는 적어도, 비밀번호 자체는 적지 않는다.
맺음말
25년 넘게 개발하면서 가장 비싼 지식은 늘 문서에 없는 것이었다. "그 서버는 원래 그래", "그 설정은 건드리면 안 돼" 같은 것들. 사람 팀에서는 이런 지식이 선배의 머릿속에 있었다. AI와 일하는 지금은 그 자리를 메모리 파일이 대신한다.
AI에게 기억을 심는다는 건 거창한 일이 아니다. 데인 자리마다 짧은 메모를 붙여두는 것이다. 그 메모가 쌓이면, 새 세션의 Claude도 처음부터 "아, 거기 조심해야죠"라고 말하기 시작한다.
댓글 0개