Skip to content

fix: summary.ts의 git 호출도 $ 대신 Bun.spawn 헬퍼로 읽는다 - #77

Merged
say8425 merged 2 commits into
mainfrom
fix/summary-git-spawn
Sep 21, 2026
Merged

say8425 merged 2 commits into
mainfrom
fix/summary-git-spawn

Conversation

@say8425

@say8425 say8425 commented Sep 21, 2026

Copy link
Copy Markdown
Owner

#74는 showBytesBun.spawn으로 옮겼다. 남은 $ 중 **가장 위험한 곳은 summary.ts**다 — 빈 상태 카드와 자동 base 전환(워크트리에서 미커밋 0건인 흔한 상태)이 부르는 요약인데, flight 밖이라 타임아웃도 503도 재시도도 닿지 않는다. 걸리면 요약이 영영 안 온다.

실제로 걸리는지 먼저 쟀다(Bun 1.3.12, 실제 getRepoSummary 반복 호출):

baseFilesdiff --name-only 출력 순차 호출 결과
130KB (변경 3,000 파일) 800번 전부 깨끗
437KB (변경 10,000 파일) 1회차 18번째에 행업, 2회차 150번 깨끗

실재하지만 드물다. 그리고 결정론적으로 재현되지 않는다(8개 동시 호출로도 5라운드 중 간헐적).

무엇을

  • server/gitOutput.ts (신규)gitBytes(args) / gitText(args). showBytes에 있던 Bun.spawn 패턴과 그 근거·동작 계약을 한 곳으로 옮긴다: 종료 코드 무시(.nothrow()와 같음), 스폰 실패는 throw, stdout을 먼저 비운 뒤 exited(지금 Bun은 반대 순서도 교착하지 않지만 — 1.3.12에서 50MB까지 실측 — 구현 세부에 기대지 않는 관례). 호출처가 둘이 된 시점이라 헬퍼로 뺐다. 출력이 64KB를 넘을 수 있는 git 호출은 여기를 타야 한다 — 아직 $로 남은 호출(diff.tsname-status·ls-files, 지문의 status, refs.ts)은 이 PR 범위 밖이다.
  • showBytesgitBytes. 동작 동일.
  • summary.ts → 여섯 호출 전부 gitText. 파일에서 $가 사라진다. 작은 출력(branch --show-current, rev-parse --short, rev-list --count)까지 옮긴 건 "이 파일엔 $가 없다"를 눈으로 확인되는 불변식으로 만들려는 것이다. 인자는 예전에도 보간 하나가 인자 하나였고 지금은 argv라, 전달되는 값은 같다.
  • CLAUDE.md — 남은 $ 목록에서 summary.ts를 빼고 "덮지 않는 곳"에 반영, 새 git 호출 규칙이 헬퍼를 가리키게 했다. 그리고 #76에서 제가 test-bun13 문단을 끼워 넣으면서 그 뒤로 밀려 설치가 없는 잡을 설명하는 것처럼 읽히던 "bun install --frozen-lockfile." 한 마디를 "나머지 잡은 …로 설치한다"로 바로잡았다.
  • test-bun13 — 새 회귀망도 Bun 1.3.14로 돈다(의존성 없음).

TDD — RED를 어디에 걸었나

summary.ts의 행업은 결정론적으로 재현되지 않으므로 그 파일에 대한 RED 테스트는 만들 수 없다. 그래서 RED는 새 헬퍼에 걸었다: git-output.test.ts는 확실히 걸리는 모양(200KB git show 12개를 8-way로, 3라운드)으로 gitText를 찌르고, 내용을 바이트 단위로 대조한다.

Bun 1.3.12
헬퍼 없음 Cannot find module '../server/gitOutput.ts'
Bun.spawn 구현 ✅ 3/3
뮤테이션: gitBytes$ gitText 버스트가 10000ms 안에 settle하지 않았다 (2/2)

UTF-8 디코딩(한글 파일)과 종료 코드 무시(없는 rev:path"")도 단언한다.

리뷰: Critical 0 · Important 1 · Minor 5. 리뷰어가 RED($ 뮤테이션 3/3 실패)와 잘림 뮤테이션(Expected 200009 / Received 147456)을 따로 재현했다. 반영한 것 — Important 1(헬퍼 주석이 "64KB 넘는 호출은 전부 여기를 탄다"고 거짓 주장), Minor 2("exited를 먼저 기다리면 교착"은 측정 안 된 주장 — 재 보니 거짓이라 관례로 고쳐 씀), Minor 3·4(CLAUDE.md 서술 정확도). 반영 안 한 것 — beforeAll·TextDecoder 재사용(리포 관례와 무시할 비용).

⚠️ 알려진 한계: summary.ts$로 되돌리는 것은 어떤 테스트도 못 잡는다(소스를 grep하는 테스트는 쓰지 않는다). CLAUDE.md 규칙과 리뷰로 지킨다.

테스트 플랜

bun test              # 791 pass (788 + 새 3)
bun run test:coverage # 100% 게이트 통과 (gitOutput.ts 100/100)
bun run typecheck     # 통과
bun run lint          # 에러 0, 경고 21 = main과 동일 (신규 0)
bun run format:check  # 통과
# test-bun13 모의: node_modules 없는 클론에서 Bun 1.3.14로 두 파일 → 4 pass

기존 summary.test.ts 11종·empty-state.test.ts가 리팩터의 동작 보존을 지킨다(관련 파일 묶음 115 pass).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK

say8425 and others added 2 commits September 21, 2026 15:29
#74는 showBytes만 Bun.spawn으로 옮겼다. `summary.ts`(빈 상태 카드·자동 base
전환의 요약)는 flight 밖이라 타임아웃도 503도 재시도도 닿지 않는데, 출력이
64KB를 넘을 수 있는 `$`(diff --name-only, ls-files --others)가 그대로였다.
Bun 1.3.x에서 그 경로가 실제로 걸리는 것을 확인했다(변경 10k 파일 = name-only
437KB 출력에서 순차 호출 18번째에 행업). 빈도는 낮지만 걸리면 요약이 영영
안 온다.

- server/gitOutput.ts(신규): gitBytes(args)/gitText(args). showBytes에 있던
  Bun.spawn 패턴과 근거·동작 계약(종료 코드 무시, 스폰 실패는 throw, stdout을
  다 비운 뒤 exited)을 한 곳으로 옮긴다. 호출처가 둘이 된 시점이다.
- showBytes는 gitBytes를, summary.ts는 여섯 호출 전부 gitText를 쓴다 —
  summary.ts에서 `$`가 사라진다(작은 출력 호출까지 옮긴 건 "이 파일엔 `$`가
  없다"를 눈으로 확인되는 불변식으로 만들려는 것).
- 회귀망 git-output.test.ts: summary의 행업은 결정론적으로 재현되지 않으므로
  (8개 동시 호출로도 간헐) 헬퍼 자체를 확실히 걸리는 모양 — 200KB `git show`
  12개를 8-way로 — 으로 찌른다. gitBytes를 `$`로 바꾸면 1.3.12에서 2/2
  실패(settle 타임아웃), 원복하면 통과. UTF-8 디코딩과 종료 코드 무시도 단언.
  test-bun13 잡이 이 파일도 Bun 1.3.14로 돈다(의존성 없음).
- 알려진 한계: summary.ts를 `$`로 되돌리는 것은 테스트가 못 잡는다. CLAUDE.md
  규칙("64KB를 넘을 수 있으면 gitBytes/gitText")과 리뷰로 지킨다.
- CLAUDE.md: 남은 `$` 목록에서 summary.ts를 빼고 "덮지 않는 곳"에 반영한다.
  #76에서 test-bun13 문단 뒤로 밀려 모순처럼 읽히던 "bun install
  --frozen-lockfile." 한 마디를 "나머지 잡은 …로 설치한다"로 바로잡는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK
- gitOutput.ts·git-output.test.ts가 "64KB를 넘을 수 있는 git 호출은 전부
  여기를 탄다"고 했는데 거짓이다 — diff.ts의 name-status·ls-files, 지문의
  status, refs.ts의 for-each-ref가 아직 `$`다(같은 PR의 CLAUDE.md도 그렇게
  말한다). "새 호출은 여기를 탄다"는 규칙으로 고치고 남은 목록을 가리킨다.
- "stdout을 비우기 전에 exited를 기다리면 64KB 넘는 출력에서 교착한다"는
  측정하지 않은 주장이었다. 재 보니 Bun이 파이프를 선제 버퍼링해 200KB·5MB·
  50MB 모두 교착하지 않는다(1.3.12). 순서는 구현 세부에 기대지 않으려는
  관례로 고쳐 쓴다.
- CLAUDE.md: "getRepoSummary는 이제 `$`를 하나도 쓰지 않는다"는 틀렸다 —
  resolveDiffBaseRev의 merge-base가 남는다(출력이 작다). 그리고 "수백 번에
  한 번꼴"을 원시 수치(한 번은 18번째, 한 번은 150회 무사)로 바꾼다.

주석·문서만 바뀐다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK
@say8425
say8425 merged commit 03ce8d9 into main Sep 21, 2026
6 checks passed
@say8425
say8425 deleted the fix/summary-git-spawn branch September 21, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant