Migration Checklist Omission Postmortem

마이그레이션 문서 주의사항 누락 원인 분석 (postmortem) - Claude

migration 브랜치 작업 중, vue3-migration-plan.md에 이미 명시돼 있던 주의사항 몇 건이 실제 마이그레이션 커밋에서 처리되지 않고 남아있던 게 발견됨. 왜 빠졌는지에 대한 문답을 정리.

1. 발견된 누락 항목

항목plan.md 위치실제 상태처리 커밋
b-table scoped slot — b-table-column별 개별 v-slot 필요line 68MyRequestReturn2.vue에서 <b-table>을 감싸는 단일 #default 슬롯 사용 → /return-kit 진입 시 props.row undefined 크래시
b-dropdown hoverable prop 제거line 66헤더 드롭다운 3개 파일이 여전히 hoverable 사용 → hover 시 안 열림(컷오버 한 달 뒤, 증상 발견 후 처리)
잔존 .native 모디파이어line 59InputAgreements.vue에 컷오버 이후 재유입
커스텀 컴포넌트 v-model (value/inputmodelValue/update:modelValue)line 54CategoryTabs.vue, ClassTabs.vue 둘 다 Vue2 문법 잔존
this.$destroy() (Vue3에 없는 API)line 61 ($mount/$destroy 없음)GModal.vue, TabItem.vue, TabItemSub.vue 3곳 잔존 (현재는 트리거 안 되는 dead path)

b-modal activemodelValue (line 65)는 이미 별도 커밋으로 처리돼 있었음 — 즉 이번이 처음 걸린 케이스는 아니고, 같은 유형의 누락이 반복돼 온 정황.


2. 왜 빠졌는가 — 검증된 원인

2.1 "context 용량 부족" 아님

vue3-migration-plan.md(19KB, ~5천 토큰)·vue3-migration-handoff.md(44KB, ~1.1만 토큰) 모두 단일 세션의 context window에 통째로 들어가고도 남는 크기. 두 항목이 빠진 시점(handoff.md의 Step 7 TODO 작성, 커밋 0cb346b32)에도 용량 여유는 충분했음. "공간이 없어서 뺐다"는 설명은 데이터로 반박됨.

2.2 "attention이 위치상 밀렸다"도 아님

plan.md 원본 bullet(line 96)의 5개 하위 항목 중 실제로 빠진 건 2번째(앞쪽)와 5번째(맨 끝):

  1. .syncv-model:arg (135곳) — 생존
  2. 커스텀 컴포넌트 v-model 리네임 — 탈락
  3. .native 제거 — 생존
  4. boolean attr falsenull/undefined — 생존
  5. b-table scoped slot 점검 — 탈락

리스트 중간이 가장 약하게 기억된다는 lost-in-the-middle 패턴이라면 탈락 항목이 중간에 몰려야 하는데, 실제로는 앞·끝에 걸쳐 있음 → 위치 기반 설명과 안 맞음.

2.3 실제 원인 — "검증 가능성" 축으로 갈린 선택적 누락

생존한 1·3·4번은 전부 "패턴 X를 찾아 Y로 치환, N곳"처럼 grep으로 개수를 세고 완료를 증명할 수 있는 항목. 탈락한 2·5번은 파일을 열어 구조/의미를 판단해야 완료 여부를 알 수 있는 항목. 위치가 아니라 "완료를 증명하기 쉬운가"가 생존을 갈랐음.

2.4 손실이 일어난 지점 특정

git 기록 대조 결과, 손실은 plan.md 자체가 아니라 plan.md → handoff.md로 옮겨적는 요약 단계에서 발생:

xxxx  2026-07-01 10:21:48  docs(migration): handoff에 Phase 1 TODO 목록 추가   (Claude Opus 4.8)
yyyy  2026-07-01 12:48:02  chore(migration): Step 7 템플릿 기계적 치환          (Claude Sonnet 4.6)

handoff.md의 Step 7 항목을 작성한 세션과 그걸 보고 실행한 세션이 서로 다른 모델. 즉 Step 7 실행 세션은 plan.md 원문을 보고 두 항목을 빼먹은 게 아니라, 이미 두 항목이 빠진 축약본을 보고 그대로 충실히 실행했을 가능성이 큼.

2.5 handoff(세션 간 요약) 자체는 불가피했음

이 브랜치의 실제 작업 규모:

main 대비 diff : 260개 파일, +5836/-4698줄
기간           : 2026-06-08 ~ 2026-08-05 (약 2개월)
커밋 수         : 137개
관여 세션(모델) : Sonnet 4.5 ×50, Sonnet 5 ×29, Sonnet 4.6 ×15, Opus 4.8 ×2 (최소 4종)

이 정도 규모는 한 세션의 context window 크기와 무관하게, 애초에 여러 세션에 걸쳐 진행될 수밖에 없는 작업량. 세션 간 기억이 끊기는 이상 상태를 넘겨주는 handoff 문서 자체는 필요했고, "context가 더 컸으면 handoff 없이 한 번에 끝냈을 것"이라는 가정은 성립하지 않음.

결론: handoff라는 메커니즘이 필요했던 이유는 (넓은 의미의) context/기억 제약이 맞지만, 그 handoff 과정에서 구체적으로 항목이 샌 이유는 용량 부족이 아니라 요약 시 완전성을 검증하는 절차가 없었던 것.

2.6 정황상 기여 요인 — 기본 간결함(conciseness) 편향

사용자가 "간결하게 써달라"고 요청한 적이 없는데도 압축이 일어난 것에 대해: Claude Code 시스템 프롬프트 자체에 대화 응답을 짧게 유지하라는 지시("Your responses should be short and concise", 불필요한 문서·추상화 생성 자제 등)가 기본값으로 항상 적용됨. 이 지시는 원래 채팅 응답을 장황하지 않게 하려는 목적이지만, "정리해서 문서로 남겨줘" 같은 산출물 작성 작업에도 같은 본능이 새어 들어갈 가능성이 있음. 채팅 응답은 간결한 게 맞지만, 다음 세션이 그것만 보고 판단하는 handoff 체크리스트는 완전성이 더 중요함 — 목적이 다른데 같은 편향이 적용되면서 문제가 생겼을 가능성.


3. 다음 지시에 참고할 점

  1. 요약/handoff 문서를 만들게 시킬 땐 "원본의 모든 하위 항목을 1:1로, 누락·병합 없이 옮겨라"고 변환 규칙을 명시.
  2. 완료 후 자체 검증을 요구: "원본 문서의 항목 수와 handoff 문서의 항목 수를 대조해서 몇 개 중 몇 개가 반영됐는지 보고하라."
  3. bullet 하나에 성격이 다른 하위 항목(기계적 치환 vs 판단이 필요한 점검)이 섞여 있으면, 먼저 원자적 항목으로 쪼개고 나서 체크리스트화하도록 지시.
  4. 빌드/타입체크로 안 걸리는 항목(런타임 동작, 의미론적 리팩터)은 "완료로 표시됐다"를 신뢰하지 말고, 실제로 화면을 띄워 확인하라는 지시를 별도로 명시. (이번 재검사에서 "다 검사해라"를 반복 지시한 게 실제로 유효했던 방식.)
  5. 가능하면 재작성(요약)을 아예 생략하고, 원본 문서에 체크박스를 직접 달아 그대로 handoff 문서로 사용하는 방법도 검토 — 재작성 단계 자체를 없애면 그 단계에서 생기는 손실도 없어짐.

3.1 원본 line 위치를 handoff 항목에 남기는 것 — 효과는 있으나 단독으론 불충분

handoff 항목마다 "plan.md:96 참고"처럼 원본 line 위치를 같이 적어두면 감사(audit) 비용은 확실히 줄어든다. 이번 postmortem을 쓰면서 plan.md와 handoff.md를 사람이 손으로 문장 대조해야 했는데, line 참조가 있었으면 그 작업이 훨씬 빠르거나 기계적으로 가능했을 것.

다만 근본 방지책은 아니다 — 이번에 빠진 두 항목은 plan.md의 같은 한 줄(line 96)에 다른 3개 항목과 나란히 있었다:

5. 인벤토리 + 템플릿 기계 변환: .sync(49파일)·커스텀 v-model·.native 사용처 목록화 후 변환
   — .sync→v-model:arg(135곳), 커스텀 컴포넌트 v-model prop/event 리네임, .native 제거,
   boolean attr false→null/undefined, b-table scoped slot 3파일 점검.

handoff 항목에 "line 96 참고"라고 달아놔도, 그 줄 안의 5개 절 중 3개만 뽑아 적고 "line 96 참고"라고 쓰면 그대로 통과한다. line 참조는 "어디서 왔는지"는 보장하지만 "그 줄의 내용을 다 옮겼는지"는 보장하지 않는다. 손실이 "다른 줄을 안 봐서"가 아니라 "같은 줄 안에서 일부만 골라서" 생겼기 때문에, line 단위 참조만으로는 못 잡는다.

효과를 보려면 다음과 같이 조합해야 한다:

  1. line 참조 + 그 줄/항목 안의 하위 절 개수를 명시 (예: "line 96, 하위 5개 항목 중 5개 반영") — "5개 중 3개"라고 쓰다가 스스로 걸리거나, 감사 시 숫자 불일치가 바로 드러남.
  2. 원본 문서 자체를 한 줄에 하나의 체크 가능한 항목만 오도록 먼저 쪼개는 것 (3장 3번과 동일) — 이러면 line 참조가 자연스럽게 항목 단위와 1:1이 되어 실제로 효과를 냄.

대화 원문 (commit id 등은 제거)

❯ 문서에 명시됐는데 처리 안하는 현상의 원인이 뭐야? context 문제야? attention 문제야? ❯ 원인을 알아야 다음 번 지시에 참고할 수 있다.

정확히 말하면 둘 다 아니고, 두 가지 다른 실패 지점이 겹친 겁니다.

  1. 제 경우(오늘 이 세션) — context/attention이 아니라 "찾아볼 생각을 안 함"

/return-kit 버그를 처음 조사할 때 저는 node_modules/buefy 소스를 까서 역산하는 방식으로 원인을 찾았지, docs/GIT-5947/ 폴더를 검색조차 안 했습니다. 그 시점에 제 context window는 전혀 안 찼었고(대화 초반), 문서도 130줄짜리라 통째로 넣어도 부담 없는 크기였습니다. 즉 "넣을 자리가 없어서" 놓친 게 아니라 "찾아야 한다는 판단 자체를 안 한" 것 — retrieval 실패입니다. CLAUDE.md에 이 문서 경로가 언급돼 있지 않았고, 사용자가 먼저 보여주기 전까진 이 문서의 존재를 몰랐습니다.

  1. Step 7 커밋(과거 세션) — bullet을 실행 단위로 쪼갤 때 선택적으로 누락

이건 다른 메커니즘입니다. Step 7 커밋 메시지를 보면 이 세션은 분명 plan 문서를 읽고 작업했습니다(문서의 문구·수치를 그대로 인용). 그런데 plan 문서 한 bullet 안에 성격이 다른 항목들이 섞여 있었죠:

▎ .sync→v-model:arg(135곳), 커스텀 컴포넌트 v-model 리네임, .native 제거, boolean attr, b-table scoped slot 3파일 점검

이 중 .sync/.native/beforeDestroy/slot= 4개는 "패턴 X를 찾아서 Y로 치환, N곳"처럼 grep으로 카운트 가능하고 결과를 정량적으로 보고할 수 있는 작업입니다. 실제 커밋 메시지도 정확히 그 4개만, 정확한 개수(132/31/21/12)와 함께 나열돼 있습니다. 반면 "커스텀 컴포넌트 v-model 리네임"과 "b-table 점검"은 "몇 곳"이라고 셀 수 있는 게 아니라 각 파일을 읽고 판단해야 하는 작업이라 grep 결과로 완료를 증명할 수 없습니다. 이 bullet을 실행 가능한 태스크로 바꾸는 과정에서, 계량화 가능한 하위 항목만 뽑히고 나머지는 소리 없이 빠진 겁니다. 이건 "주의력 부족"이라기보다 "완료를 증명하기 쉬운 일이 완료를 증명하기 어려운 일을 밀어낸다"는 선택 편향에 가깝습니다.

  1. 이걸 계속 통과시킨 진짜 원인 — 잡아주는 장치가 없었음

가장 중요한 건 이겁니다: 두 항목 다 pnpm build가 그냥 통과합니다. 문법 오류가 아니라 런타임 동작 오류라 타입체크도, 빌드도, 린트도 안 걸립니다. 테스트 커버리지도 없고요. 즉 "빠뜨려도 아무것도 안 터지니 아무도 모른다"는 상태가 계속 방치될 수 있는 구조였습니다. 사람이든 AI든, 완료 여부를 스스로 검증할 방법이 없는 작업은 스코프에서 조용히 빠지기 쉽습니다.

다음 지시에 참고할 점

  • 이런 문서를 다시 참조시킬 땐 "이 표/체크리스트를 다 처리해"가 아니라 "각 행을 개별적으로 완료 처리(커밋 또는 체크)하고, 안 되는 이유가 있으면 명시하라"고 항목 단위로 닫도록 지시하는 게 낫습니다.
  • 특히 bullet 하나에 "치환"과 "점검"이 섞여 있으면, 먼저 그 bullet을 원자적 항목으로 쪼개 달라고 시킨 뒤 실행시키는 게 누락을 줄입니다.
  • 빌드/타입체크로 안 걸리는 항목(런타임 동작·의미론적 리팩터)은 애초에 "완료됐다고 믿지 말고 실제로 라우트를 띄워서 확인하라"는 지시를 명시적으로 붙이는 게 좋습니다 — 이번처럼 "다 검사해라"를 반복해서 시키는 게 실제로 유효했던 이유가 이겁니다.

✻ Crunched for 51s

❯ 1번은 당연한거고. 묻는 건 오늘 수정이 아니라 이전에 migration할 때 문서 제공을 했는데 migration 작업에서 빠뜨린 원인을 묻는거다.

Ran 1 shell command

좋은 지적입니다 — 다시 파보니 실제로 "어디서" 빠졌는지 문서 간 비교로 특정됩니다. 결론부터 말하면 context/attention 문제가 아니라, 문서를 요약해서 옮겨적는 과정에서 생긴 손실(lossy hand-off)입니다.

증거

원본 계획 문서 vue3-migration-plan.md:96 (Step 5 bullet):

▎ .sync→v-model:arg(135곳), 커스텀 컴포넌트 v-model prop/event 리네임, .native 제거, boolean attr false→null/undefined, b-table scoped slot 3파일 점검

그런데 이걸 실행용으로 옮겨적은 vue3-migration-handoff.md:96 (Step 7 체크리스트):

▎ .sync(135곳) → v-model:arg, .native 제거, beforeDestroy → beforeUnmount, Vue 2 slot 구문(slot=/slot-scope=) → v-slot, boolean attr false → null/undefined

두 항목이 옮겨적는 과정에서 그냥 빠져있습니다 — "커스텀 컴포넌트 v-model 리네임"과 "b-table 점검"이 통째로 사라졌어요. 그리고 실제 Step 7 실행 커밋의 메시지를 보면:

  • .sync(132곳) → v-model:arg
  • .native(31곳) 제거
  • beforeDestroy(21곳) → beforeUnmount
  • slot="name"(12곳) → #name (Private)
  • slot-scope="var"(3곳) → #default= (Private)"var"

이 5개 카테고리는 plan.md 원문이 아니라 handoff.md의 축약된 문구와 정확히 일치합니다(원문엔 있는 "커스텀 v-model 리네임" 카테고리가 커밋 메시지엔 애초에 없음). 즉 Step 7을 실행한 세션은 plan.md 원문을 보고 두 항목을 빠뜨린 게 아니라, 이미 두 항목이 빠진 handoff.md 체크리스트를 보고 그걸 충실히 실행했을 가능성이 큽니다. 그 세션 입장에서는 "context에 있었는데 못 봤다"가 아니라 애초에 "그 항목이 눈앞에 없었다"는 쪽에 가깝습니다.

손실이 어디서 왜 일어났나 — plan.md의 bullet 하나에 성격이 다른 하위항목 5개가 섞여 있었는데, handoff.md로 재정리(요약)하면서 "grep 개수로 딱 떨어지는 4개"만 살아남고 "파일을 읽고 판단해야 하는 2개"(커스텀 v-model, b-table)가 요약 과정에서 탈락했습니다. 이건 앞서 말씀드린 "계량화 가능한 일이 계량화 어려운 일을 밀어낸다"는 선택 편향이 실행 단계가 아니라 문서를 요약하는 단계에서 먼저 일어난 겁니다.

참고로 다른 실패 패턴도 하나 있음 — b-modal v-model:active는 handoff.md 체크리스트엔 살아있었고(Step 5 항목) Step 5 실행 커밋도 시도는 했지만, 일부 파일만 처리하고 끝났습니다. 나중에 "누락분 수정"이 따로 필요했죠. 이건 요약 손실이 아니라 "체크리스트엔 있었는데 실행이 부분적이었고, 완료 여부를 검증 안 하고 다음으로 넘어간" 별개의 실패입니다.

정리: 두 패턴 다 "모델이 이미 준 정보를 못 읽었다"가 아니라 (A) 원본 문서를 실행용 체크리스트로 재정리하는 단계에서 정보가 누락됐거나, (B) 체크리스트 항목을 부분적으로만 처리하고도 완료로 표시한 것입니다. 다음번엔 이렇게 지시하시는 게 도움될 것 같습니다:

  • 요약/체크리스트를 새로 만들게 시킬 땐 "원본 문서와 항목 개수가 일치하는지 대조하라"는 지시를 끼워넣기
  • 체크리스트 항목을 실행시킬 땐 완료 후 "원본 bullet의 하위 항목을 전부 커버했는지 원문과 다시 대조"하도록 시키기 (grep 개수만 보고 끝내지 않도록)

✻ Baked for 1m 5s

❯ context 제약 때문에 요약이 필요했고, attention 에서 밀려서 누락된 걸로 보이는데 아니라고?

Ran 1 shell command

근거를 보면 두 가지 다 정확한 설명은 아닙니다.

"context 제약" — 파일 크기로 반박됨

vue3-migration-plan.md 19,477 bytes (~5,000 토큰) vue3-migration-handoff.md 44,322 bytes (~11,000 토큰)

두 문서를 합쳐도 2만 토큰이 안 됩니다. 관련 문서 8개를 전부 합쳐도(bugfixes.md, jquery-removal-plan.md 등 포함) 13만 바이트, 대략 3만 토큰대입니다. 지금 이 대화도 이미 그보다 훨씬 많은 내용을 주고받았고 문제없이 돌아가고 있죠. 이 정도 분량은 "요약 안 하면 다 못 넣는" 수준이 전혀 아닙니다. "공간이 없어서 압축했다"는 전제 자체가 근거가 약합니다.

"attention에서 밀렸다" — 위치 기반이면 오히려 반대 결과가 나와야 함

plan.md:96 bullet 안에서 5개 항목의 순서는:

  1. .sync→v-model:arg (135곳) — 살아남음
  2. 커스텀 컴포넌트 v-model 리네임 — 탈락
  3. .native 제거 — 살아남음
  4. boolean attr false→null/undefined — 살아남음
  5. b-table scoped slot 점검 — 탈락

"attention이 밀렸다"(예: lost-in-the-middle 현상 — 리스트 중간 항목이 덜 회상되는 것)가 원인이라면, 탈락한 항목은 리스트 중간에 몰려 있어야 합니다. 그런데 실제로 탈락한 건 2번(앞쪽)과 5번(맨 끝)이고, 살아남은 건 1, 3, 4번(앞~중간)입니다. 이건 위치 기반 attention 손실 패턴과 안 맞습니다.

대신 정확히 맞아떨어지는 축은 "grep으로 개수를 셀 수 있는가"입니다 — 살아남은 1·3·4번은 전부 "패턴 X를 찾아 Y로 치환, N곳"처럼 정량화되는 항목이고, 탈락한 2·5번은 "파일을 읽고 구조를 판단해야" 완료 여부를 알 수 있는 항목입니다. 위치가 아니라 항목의 성격(검증 가능성)이 생존을 결정했다는 뜻이라, "context가 부족해서 attention이 밀렸다"보다는 "완료를 증명하기 쉬운 항목이 우선 선택되고, 증명하기 어려운 항목은 요약 단계에서 잘려나갔다"는 선택 편향 쪽이 더 정확한 설명입니다.

정리하면: context 용량 문제는 데이터로 반박되고, "attention이 밀렸다"도 순수 위치 효과로는 설명이 안 됩니다. 실질적 원인은 문서를 압축할 때 "검증 가능한 작업"과 "판단이 필요한 작업"을 구분하지 않고 후자를 통째로 버린 것 — 이건 용량 부족이 아니라 요약 기준 자체의 결함입니다.

✻ Brewed for 45s

❯ context 용량이 충분한데 요약을 왜하는건데?

Ran 1 shell command

핵심을 짚으셨는데, 실제로 "요약을 왜 했는가"의 답은 context 용량과는 다른 이유였습니다. git 기록으로 정확히 특정됩니다.

Ran 1 shell command

git 기록으로 정확히 확인됩니다 — 요약 자체는 이번 대화의 context 문제가 아니라 세션 간 상태 전달(hand-off) 때문에 만들어진 겁니다.

xxxx 2026-07-01 10:21:48 docs(migration): handoff에 Phase 1 TODO 목록 추가 (Claude Opus 4.8) xxxx 2026-07-01 12:48:02 chore(migration): Step 7 템플릿 기계적 치환 (Claude Sonnet 4.6)

요약을 만든 세션(10:21, Opus 4.8)그걸 실행한 세션(12:48, Sonnet 4.6)이 서로 다릅니다. 이게 요약이 필요했던 진짜 이유입니다 — Claude Code 세션은 대화가 끝나면 기억이 안 남습니다. plan.md 130줄짜리 연구 문서를 다음 세션이 매번 처음부터 다시 읽고 "지금 뭘 해야 하나"를 재추론하게 만드는 것보다, "Step 7: .sync(135곳)→v-model:arg, .native 제거..." 처럼 한 줄로 바로 실행 가능한 TODO를 만들어 넘겨주는 게 다음 세션의 재추론 비용을 줄이는 합리적인 선택입니다. 이건 이번 세션의 context가 부족해서가 아니라, 다음 세션이 매번 원본을 다시 읽지 않게 하려는 목적입니다.

그런데 여기서 중요한 지점: 이 요약을 만들 때도 context 용량은 전혀 부족하지 않았습니다 (plan.md 5천 토큰, handoff.md 만들 때도 여유). 그런데도 두 항목이 빠졌다는 건, "공간이 없어서 뺐다"가 아니라 "체크리스트 한 줄 형식(항목명 + 개수)에 맞추려다 보니, 개수로 안 떨어지는 항목(판단이 필요한 항목)을 편집 과정에서 쳐냈다"는 뜻입니다. 즉 원인은 용량이 아니라 요약이 지향한 포맷(간결한 grep-스타일 체크리스트)이 애초에 "판단해야 하는 항목"을 담기에 안 맞았고, 그걸 억지로 욱여넣기보다 조용히 생략하는 쪽을 택한 것입니다.

정리하면:

  • 요약이 필요했던 이유: 세션 간 기억 단절 → 다음 세션의 재추론 비용을 줄이기 위한 정상적인 실행용 산출물
  • 요약에서 항목이 빠진 이유: 용량 부족이 아니라, 그 요약 포맷 자체가 "정량화 가능한 항목"에 최적화돼 있어서 "구조적 판단이 필요한 항목"이 형식에 안 맞아 누락됨

다음 지시에 참고하시려면: 이런 handoff/TODO 문서를 만들게 시킬 때 "원본 대비 항목 수가 같은지 명시적으로 대조해서 보고하라"는 조건을 붙이시는 게, "context를 더 주는 것"보다 훨씬 효과적일 겁니다 — 용량은 원래도 충분했으니까요.

✻ Sautéed for 1m 20s

❯ handoff 과정에서 누락될 수 있다는 건 이해했다만... handoff를 안했으면 context 용량으로 다 커버할 수 있는 작업인가? 현재 브랜치 마이그레이션 작업이? 아닐거 같은데.

Ran 1 shell command

아니요, 못 덮습니다 — 숫자로 보면 명확합니다.

main 대비 diff: 260개 파일, +5836/-4698줄 기간: 2026-06-08 ~ 2026-08-05 (약 2개월) 커밋 수: 137개 관여 세션(모델): Sonnet 4.5 ×50, Sonnet 5 ×29, Sonnet 4.6 ×15, Opus 4.8 ×2 (최소 4종, 96개 커밋에 명시)

260개 파일·1만 줄 넘는 변경을 2개월에 걸쳐 최소 4개의 서로 다른 세션/모델이 나눠서 처리했습니다. 이건 "context window가 더 컸으면 한 세션에서 끝냈을 일"이 아닙니다 — 애초에 세션이 유지되는 동안(한 대화) 할 수 있는 작업량과, 이 마이그레이션 전체 스코프 사이에 구조적으로 넘사벽 차이가 있습니다. 대화 하나가 몇 시간~며칠씩 이어질 수 없고, 이어진다 해도 그 안에서 260개 파일을 순서대로 열어보고 고치고 빌드 검증까지 하는 건 context 크기와 별개로 현실적으로 불가능한 규모입니다.

그러니 "handoff를 안 했으면"이라는 가정 자체가 성립 안 합니다 — 이 정도 규모의 마이그레이션은 반드시 여러 세션에 걸쳐 진행돼야 하고, 그러면 반드시 세션 간 상태를 옮겨주는 문서(handoff)가 있어야 합니다. context를 아무리 키워도 "한 세션 안에서 다 끝내기"는 답이 아니고, 여러 세션에 걸쳐 진행하는 게 맞는 방식이었습니다.

즉 진짜 질문은 "handoff를 없앨 수 있냐"가 아니라 "handoff가 필연적이라면, 그 과정에서 정보가 새는 걸 어떻게 막느냐"입니다. 이게 애초에 제가 드린 제안(원본 대비 항목 수 대조)의 이유이기도 하고요. 추가로 이번에 나온 증거를 반영해서 좀 더 구체적으로 제안하면:

  • 요약을 시킬 때 "정리해줘" 같은 열린 지시 대신, "원본 표의 N개 행을 각각 1:1로 체크리스트 한 줄씩 변환해라, 병합하거나 생략하지 마라"처럼 변환 규칙을 기계적으로 못박기
  • handoff 문서 자체에 "원본 대비 항목 수: OO/OO" 같은 대조 카운트를 남기게 해서, 나중에 누가 봐도 손실 여부가 한눈에 보이게 하기
  • 정 안되면, 애초에 요약본을 새로 안 만들고 원본 문서에 체크박스를 직접 달아 그걸 그대로 handoff 문서로 쓰기 (재작성 자체를 없애면 재작성 중 손실도 없음)

✻ Cooked for 43s

❯ 결국 context 제약 때문이잖아

맞습니다, 한 발 물러서서 보면 그 말이 맞습니다. 정리하겠습니다.

구조적 원인 — 맞음, context/memory 제약 세션이 대화 하나로 끝나면 기억이 사라지고, 다음 대화는 파일을 안 읽으면 아무것도 모르는 상태로 시작합니다. 이 "세션 간 기억 단절"이야말로 넓은 의미의 context/memory 제약이고, 이것 때문에 애초에 handoff 문서라는 메커니즘 자체가 필요해진 겁니다. 이 층위에서는 제가 부정할 근거가 없고, 말씀하신 대로입니다.

하지만 실제 손실이 일어난 지점 — 여기는 다름 다만 실제로 두 항목이 빠진 그 순간(handoff.md 작성 시점, 10:21)엔 plan.md 5천 토큰을 다 넣고도 자리가 남는 상황이었습니다. "이 세션의 창이 좁아서 어쩔 수 없이 뺐다"가 아니라, 자리가 남는데도 형식(간결한 체크리스트 한 줄)에 맞추다 빠뜨린 겁니다.

이 구분이 왜 중요하냐면 — "context 제약 때문"이라고 결론 내리면, 다음번 해법이 "context를 더 준다"가 돼버립니다. 근데 그건 이 문제를 안 고칩니다. 다음에 handoff 문서를 또 만들 때도 context는 여전히 남아돌 거고(원본 문서가 몇만 토큰씩 되는 게 아니니까), 그런데도 같은 방식으로 "간결하게 정리해줘"라고 시키면 또 판단이 필요한 항목이 조용히 빠질 겁니다.

그러니 두 층위를 나눠서 보시는 게 맞을 것 같습니다:

  • 왜 handoff가 필요했나 → 세션/기억 제약, 맞음, 손댈 수 없는 구조
  • 왜 handoff 안에서 항목이 샜나 → 용량이 아니라 "요약 지시가 완전성을 요구하지 않았고, 완전성을 검증하는 절차가 없었던" 것 → 이건 다음번 지시 방식으로 고칠 수 있는 부분

그래서 실질적으로 참고하실 부분은 여전히: handoff/요약을 시킬 때 "항목을 빠뜨리지 말고 원본과 개수를 대조해서 보고하라"는 명시적 검증 조건을 넣는 것입니다 — context를 늘리는 게 아니라, 있는 context를 다 쓰라고 강제하는 것.

✻ Sautéed for 30s

❯ handoff 작성해달라고 하지 간결하게 작성해달라고 하진 않았을텐데, handoff 작성시 추가되는 기본 프롬프트인가?

맞는 의심입니다. 제가 과거 세션의 내부를 직접 들여다볼 순 없지만(다른 대화의 추론 과정은 로그로 안 남고, 지금 저도 접근 불가), 제가 지금 이 순간 따르고 있는 시스템 프롬프트 자체에 그 편향이 명시돼 있는 걸 확인할 수 있습니다.

제 시스템 프롬프트에는 이런 지시들이 있습니다:

▎ "Your responses should be short and concise." ▎ "Don't add features, refactor, or introduce abstractions beyond what the task requires... Three similar lines is better than a premature abstraction." ▎ "Don't create planning, decision, or analysis documents unless the user asks for them"

이건 원래 대화창 응답(채팅 답변)을 장황하게 하지 말라는 취지로 들어간 규칙들인데, "간결하게 써라"는 게 이 도구 자체의 기본값으로 항상 켜져 있는 겁니다. 사용자가 매 요청마다 "간결하게"라고 안 붙여도, 이 성향이 기본 상태(default)라서, "handoff 문서 작성해줘"처럼 파일 작성을 요청했을 때도 같은 절약 본능이 새어 들어갈 가능성이 높습니다 — 원래 이 지시들은 "쓸데없는 서두/맺음말 넣지 마라" 같은 대화용 규칙인데, "정리해서 문서로 남겨줘" 같은 작업에도 그 본능이 그대로 적용돼서 "완전성보다 간결함이 기본값"이 되어 버리는 거죠.

문제는 이 두 상황의 목적이 정반대라는 겁니다:

  • 채팅 답변: 간결한 게 맞음 (사용자가 다 읽어야 하니까)
  • handoff 체크리스트: 완전한 게 맞음 (다음 세션이 그것만 보고 판단하니까, 빠지면 그 항목은 존재 자체가 사라짐)

같은 "간결함" 본능이 두 군데에 똑같이 적용되면서, 후자에서 문제가 생긴 걸로 보입니다. 그러니 이건 "그 세션이 실수로 대충 했다"가 아니라 Claude Code라는 도구 자체의 기본 성향이 문서 작성에도 새어 들어가는 구조적 문제일 가능성이 큽니다.

실질적 대응: handoff/체크리스트 문서를 시킬 때는 명시적으로 "간결하게 쓰지 말고, 원본의 모든 하위 항목을 빠짐없이 포함해라. 항목 수를 원본과 대조해서 보고해라"처럼, 이 도구의 기본 간결함 본능을 역으로 끄는 지시를 넣어주시는 게 필요해 보입니다. 그냥 "handoff 문서 작성해줘"만으로는 이 기본값이 계속 적용될 겁니다.

❯ 문서 기반으로 작업할 때 handoff에 원본 문서의 작업 중 line 위치 같이 추가하도록 하면 효과적이겠지?

도움은 되는데, 이번 케이스만 놓고 보면 그것만으론 안 막혔을 가능성이 높습니다. 이유를 짚어보면:

효과 있는 부분

  • 지금 이 postmortem을 쓰려고 저는 plan.md와 handoff.md를 사람이 손으로 텍스트 대조해야 했습니다. line 번호가 handoff 항목에 박혀 있었으면 그 대조가 기계적으로(또는 최소한 훨씬 빠르게) 가능했을 겁니다.
  • 나중에 사람이 감사(audit)할 때 "이 handoff 항목이 정말 원본을 다 반영했나"를 확인하는 비용이 크게 줄어듭니다.
  • 소스가 스크립트로 훑기 좋아집니다 (예: handoff에 언급 안 된 원본 line을 자동으로 찾아내는 것도 가능).

안 막히는 이유 — 근본 문제는 "줄 하나에 항목 여러 개"

이번에 빠진 두 항목(커스텀 v-model 리네임, b-table 점검)은 plan.md 같은 한 줄(line 96)에 다른 3개 항목과 나란히 있었습니다:

  1. 인벤토리 + 템플릿 기계 변환: .sync(49파일)·커스텀 v-model·.native 사용처 목록화 후 변환 — .sync→v-model:arg(135곳), 커스텀 컴포넌트 v-model prop/event 리네임, .native 제거, boolean attr false→null/undefined, b-table scoped slot 3파일 점검.

handoff 항목에 "line 96 참고"라고 달아놨어도, 그 줄 안의 5개 절 중 3개만 뽑아 적고 "line 96 참고"라고 써버리면 그대로 통과합니다. line 참조는 "어디서 왔는지"는 보장하지만 "그 줄의 내용을 다 옮겼는지"는 보장 안 합니다. 실제 손실이 "다른 줄을 안 봐서"가 아니라 "같은 줄 안에서 일부만 골라 옮겨서" 생겼기 때문에, line 단위 참조로는 못 잡습니다.

더 효과적인 조합

  1. line 참조 + 그 줄/항목 안의 하위 절 개수를 명시 ("line 96, 5개 하위항목 중 5개 반영" 처럼) — 이러면 "5개 중 3개"라고 쓰다가 스스로 걸리거나, 감사할 때 숫자가 안 맞으면 바로 보임.
  2. 원본 문서 자체를 한 줄에 하나의 체크 가능한 항목만 오도록 먼저 쪼개는 것 — 이러면 line 참조가 자연스럽게 항목 단위와 1:1이 돼서 말씀하신 방식이 그대로 효과를 냅니다.

정리하면: line 위치 추가는 감사(audit)를 쉽게 만드는 데는 확실히 효과적이라 넣는 게 맞는데, 이번 같은 "한 줄에 여러 항목 뭉쳐있는" 문서 구조에선 그것만으론 근본 방지가 안 되고, 항목 개수 대조나 원본 구조 자체를 쪼개 postmortem 문서에 이 내용추가해서 커밋할까요?


Backlinks