Obsidian 플러그인으로 raw/2026/Q3/ 폴더에 새로 넣어둔 파일 20개를 /ingest했다. 결과는
깔끔했다 — 20개 전부 성공, 누락 0개. 그런데 바로 이어서 /status를 돌렸더니 전혀 관련
없는 파일 하나가 “대기 중”으로 떴다.
⏳ 대기 중인 파일 목록 (인제스트 미완료)
- 2026 (1개)
- 2026/Q2/Good Feedback Is a Two-Way Conversation.md
Q3 파일을 20개 넣었는데 왜 Q2 파일이 걸리지? 방금 끝난 작업이랑 아무 상관이 없어 보이는 파일이었다.
백링크도, 로그도 없었다
Obsidian에서 이 파일 내용(“Good Feedback”)으로 전체 검색을 해봤다. 검색 결과 7개 중에 이 raw 파일 자신의 제목 매치 하나, 완전히 다른 노트 하나가 전부였다. 이 파일을 인용하는 wiki 문서는 하나도 없었다.
혹시나 해서 검색 범위를 wiki 폴더로만 좁혀봤다.
path:wiki "Good Feedback"
→ 0 results
인제스트 처리 이력이 쌓이는 wiki/log.md에서도 찾아봤다.
path:"wiki/log.md" "Good Feedback"
→ 0 results
둘 다 없었다. 이건 지난번(파일명 한 단어가 빠져서 링크가 깨졌던 케이스)과는 성격이 달랐다. 그때는 어쨌든 어떤 wiki 문서가 이 파일을 인용은 하고 있었고, 그 인용이 살짝 틀렸을 뿐이었다. 이번엔 인용 자체가 어디에도 없었다 — 이 파일은 시스템 어디에도 “이걸 요약했다”는 흔적을 전혀 안 남기고 있었다.
원인 — 덮어쓸 때 옛날 출처를 챙기지 않는다
코드를 열어봤다. 기존 wiki 문서에 새 내용을 병합하는 update_file 명령의 처리 로직은
이랬다.
} else if (funcName === 'update_file') {
if (existingFile instanceof TFile) {
await app.vault.modify(existingFile, cleanedContent);
기존 파일이 있으면 AI가 보낸 새 콘텐츠로 그냥 통째로 덮어쓴다. 문제는 이 새
콘텐츠의 source: 필드다. AI가 문서를 업데이트할 때 “이번에 새로 병합하는 raw 파일”만
적어 보내고, 그 문서가 원래 갖고 있던 다른 출처를 다시 언급하지 않으면 — 덮어쓰는 순간
그 옛날 출처가 통째로 사라진다.
정황을 맞춰보면 이렇다. 어느 시점에 “Good Feedback…”이 어떤 wiki 문서에 정상적으로 병합돼 있었다. 그 뒤 다른 raw 파일이 같은 문서에 또 병합되면서, AI가 이번 것만 적어 보냈고 코드는 그걸 의심 없이 그대로 덮어썼다. 처음 병합됐을 당시엔 정상적으로 출처가 있었을 테니 그때 인제스트 보고는 “성공”으로 집계됐을 거고, 그 뒤로는 아무도 몰랐던 거다.
이 코드, 사실 한 달 전부터 있었다
여기서 궁금해진 게 있었다. 이 버그가 최근에 생긴 회귀인지, 원래부터 있던 건지. git log -S로 이 update_file 처리 로직이 언제 처음 생겼는지 뒤져봤다.
2026-06-14 feat(dispatcher): add multi-command parsing and UI text masking
한 달 가까이 된 코드였다. 최근에 뭘 건드려서 생긴 문제가 아니라, 이 기능이 생긴 날부터 계속 그 자리에 있었던 거다.
그럼 왜 이제야 발견됐을까. 답은 간단했다 — /status가 예전엔 개별 파일명을 안
보여주고 그냥 폴더 단위로 “101/102, 99%” 같은 퍼센트만 보여줬다. 이 정도 숫자로는
문제가 하나 숨어 있어도 눈에 안 띈다. 며칠 전 “대기 중인 파일 목록”이라는 기능을
새로 추가하면서 처음으로 어떤 파일이 걸려 있는지 이름이 찍히기 시작했다. 버그는 한
달 내내 조용히 데이터를 지우고 있었는데, 그걸 잡아낼 도구가 이번에야 생긴 거다.
수정 — 덮어쓰기 전에 합치기
update_file이 실제로 덮어쓰기 전에, 이전 버전의 출처와 새 버전의 출처를 합집합으로
병합하는 안전망을 넣었다.
function preserveDroppedSources(previousContent: string, newContent: string): string {
const oldPaths = extractSourceRawPaths(readSourceRaw(oldFrontmatter));
const newPaths = extractSourceRawPaths(readSourceRaw(newFrontmatter));
const missing = oldPaths.filter(p => !newPaths.has(normalize(p)));
if (missing.length === 0) return newContent;
// 새 출처 + 빠진 옛날 출처를 합쳐서 frontmatter에 다시 써넣는다
...
}
AI가 옛날 출처를 다시 언급하지 않아도, 최소한 이미 확보하고 있던 출처는 절대 사라지지
않게 만드는 방식이다. 재현 테스트도 하나 추가했다 — 출처 A를 가진 기존 문서에
update_file로 출처 B만 적은 새 콘텐츠를 보내면, 최종 문서엔 A와 B가 둘 다 남아있는지
확인하는 테스트다.
같은 세션에 발견한 사소한 표시 버그(“Related pages” 섹션이 문서 하나에 두 번 뜨는
문제) 하나도 같이 고쳐서, 패치 버전으로 묶어 릴리스했다. 남아있는 “Good Feedback…”
파일은 지금 아무 데도 안 걸려 있는 상태라, 다음 /ingest를 돌리면 새 파일처럼 자동
감지돼서 정상 처리될 거다.
남는 생각
이번 버그에서 제일 흥미로웠던 건 코드 자체보다 타이밍이었다. 문제는 한 달 전부터 있었는데, 그동안 한 번도 드러나지 않았다. “성공했다”는 보고를 곧이곧대로 믿을 수 없다는 건 이미 몇 번 겪어서 알고 있었지만, 이번엔 한 걸음 더 나간 교훈이었다 — 검증 도구 자체가 없으면, 실패는 보고되지 않는 게 아니라 아예 보이지도 않는다. 이번에 우연히 “대기 중인 파일 목록”이라는 작은 기능을 먼저 만들어두지 않았다면, 이 파일은 앞으로도 계속 조용히 사라진 채로 남아있었을 거다.