Skip to content

fix: 출력이 64KB를 넘을 수 있는 나머지 git 호출도 Bun.spawn 헬퍼로 읽는다 - #78

Merged
say8425 merged 4 commits into
mainfrom
fix/remaining-git-spawn
Sep 21, 2026
Merged

say8425 merged 4 commits into
mainfrom
fix/remaining-git-spawn

Conversation

@say8425

@say8425 say8425 commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Bun 1.3.x의 $는 64KB를 넘는 stdout을 받는 호출에서 promise가 영영 settle하지 않을 수 있습니다(업스트림은 1.4.0에서 수정). #74·#77이 showBytessummary.tsgitOutput.ts 헬퍼(Bun.spawn)로 옮긴 뒤에도, 큰 출력을 받는 $가 넷 남아 있었습니다.

호출 출력이 커지는 경우
지문의 status --porcelain -z -uall untracked를 켠 워크트리 — watch가 2초마다 부른다
getDiffFilesdiff --name-status 파일이 많은 diff
getDiffFilesls-files --others untracked 파일이 많을 때
refs.tsfor-each-ref 원격 브랜치가 많은 리포
refs.tsworktree list 등록된 워크트리가 많을 때 — 디렉토리가 지워져도 prunable로 남아 약 300개면 넘는다 (리뷰에서 추가)

무엇을

  • 다섯 호출을 gitText로 옮깁니다. 인자 배치와 동작(종료 코드 무시, stderr 버림)은 이전과 같습니다.
  • 이로써 서버에 남은 $rev-parse·merge-base·gh pr view출력 크기가 리포 규모와 무관한 호출뿐입니다.
  • 자가 치유 3부품(flight 타임아웃·503·브라우저 재시도)은 그대로 둡니다. 원인을 가리지 않는 안전망이고(gh pr view는 네트워크를 기다립니다), 작은 $가 원리적으로 안전하다는 증명은 없습니다. singleFlight.ts 주석을 이 이유로 고쳤습니다.
  • 헬퍼·테스트 주석과 CLAUDE.md의 "아직 $가 남아 있다"는 서술을 현재 상태로 고쳤습니다.
  • CI test-bun13 잡에 새 회귀망을 더했습니다.

TDD — RED를 어디에 걸었나

#77에서는 summary.ts의 멈춤이 매번 재현되지 않아 파일 자체를 지키는 테스트를 못 세웠습니다. 이번엔 "큰 출력이 겹치면 거의 확정"이라는 측정을 이용해, 호출처마다 그 함수를 8번 동시에 부르는 git-large-output.test.ts를 세웠습니다. 동시 호출은 실제로 일어납니다(선택이 다른 /api/diff 요청, prewarm, watch 폴이 서로 다른 flight 키로 겹침).

  • RED: 수정 전 코드, Bun 1.3.12 → 4/4 실패(전부 "15000ms 안에 settle하지 않았다")
  • GREEN: 수정 후 → 4/4 통과(8.5초)
  • 판별력(호출을 하나씩 $로 되돌림):
되돌린 호출 잡힌 횟수
지문 status -uall 3/3
diff --name-status 3/3 (거쳐 가는 ls-files 케이스도 함께 죽음)
ls-files --others 3/3
for-each-ref 11/11 (16-way — 8-way로는 20라운드에도 3/5였다)

for-each-ref 케이스만 16-way입니다. 이 호출은 좁은 구간(8-way에서 참조 600800개 ≈ 180240KB)에서만 멈춰, 처음 8-way로는 20라운드를 돌려도 5번 중 3번만 잡혔습니다. 리뷰 지적대로 16-way로 겹침을 늘리자 첫 라운드에 11번 중 11번 잡혔습니다(macOS).

⚠️ worktree list는 옮겼지만 회귀 테스트가 없습니다. 죽은 워크트리 등록 400개로 110KB를 내게 해도 $가 8·16-way로 10라운드씩 8번 동안 한 번도 멈추지 않아, 판별할 모양을 찾지 못했습니다. 멈춤은 출력 크기만으로 정해지지 않습니다(for-each-ref도 606KB에선 안 멈췄습니다). 이 사실은 테스트 docblock과 CLAUDE.md에 적었습니다.

Linux에서의 RED (리뷰 Minor 1): 다섯 호출을 전부 $로 되돌린 임시 커밋 e37d2e8을 올렸습니다 — run 35573797466에서 test-bun13(Bun 1.3.14, ubuntu)이 4 pass / 4 fail로 빨개졌고(새 파일의 네 케이스가 전부 15초 타임아웃, 헬퍼 테스트 넷은 무관하므로 통과), 최신 Bun의 test는 초록이었습니다. lint 실패는 되돌린 커밋의 부산물(gitText import 미사용 2건)입니다. 곧바로 되돌렸습니다(9016a3e, 트리가 8c61faa와 동일).

리뷰: Critical 0 · Important 1 · Minor 6. 리뷰어가 RED 4/4·깨끗한 워크트리 8 pass·호출별 뮤테이션을 따로 재현했습니다. 반영 — Important 1(for-each-ref 16-way), Minor 1(Linux RED), 2(케이스별 테스트 상한), 3(docblock 문장), 4(CI 주석의 "10초"), 5(worktree list). 반영 안 함 — Minor 6(settleWithin 공용화, 선택 사항). 부수적으로 beforeAll에 60초 상한을 줬습니다 — 픽스처가 평소 1.2~1.8초인데 부하가 걸린 실행에서 훅 기본 상한 5초를 넘는 것을 실측했습니다.

테스트 플랜

  • bun test — 795 pass / 0 fail
  • bun run test:coverage — 100% 게이트 통과 (diff.ts·fingerprint.ts·refs.ts·gitOutput.ts 모두 100)
  • bun run lint · format:check · typecheck 통과
  • test-bun13 모의: node_modules 없는 깨끗한 워크트리에서 Bun 1.3.14로 세 파일 → 8 pass; 지문 호출을 되돌리면 그 케이스만 실패
  • Linux RED: 임시 되돌림 커밋에서 test-bun13 4 fail (run 35573797466)
  • CI 6잡 최종 — run 35574260160 3차 시도에서 전부 초록 (test-bun13: Bun 1.3.14, 8 pass)

e2e flake 기록

최종 커밋(9016a3e)의 e2e가 1·2차 시도에서 worker-highlight.e2e.ts:30 한 건으로 실패하고 3차에 통과했습니다. 원래 있던 불안정으로 판단한 근거:

  • 같은 스펙이 이 변경 에도 같은 두 모양으로 실패했습니다 — 117행 스크롤 루프의 60초 타임아웃(run 33264974090, 8/29)과 178행 "색이 결국 입혀지는가" 폴 실패(run 33361270790, 8/31). 이번 1차·2차가 각각 그 모양이었습니다.
  • 2차 실패의 트레이스에서 서버 응답은 전부 정상이었습니다(/api/refs 200·15ms, /api/diff 200, worker.js 2건 200). 멈춘 곳은 브라우저 워커의 하이라이트이고, 스펙 주석도 이 폴의 원인을 "확정하지 못했다"고 적고 있습니다.
  • 로컬에서 전체 스위트(111 passed, CI와 같은 순서)와 단독 실행 3회 모두 통과했습니다.

다만 통과한 런에서 이 스펙은 10~12초로 한계와 거리가 멀고, 과거 빈도(약 60런 중 3번)로는 두 번 연속 실패가 드문 일이라 이 변경이 확률을 올렸을 가능성을 완전히 배제하지는 못합니다 — 서버의 git 호출 방식이 브라우저 워커를 막을 경로는 찾지 못했습니다. 이 flake 자체는 별도로 조사할 가치가 있습니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK

say8425 and others added 4 commits September 21, 2026 16:09
Bun 1.3.x의 `$`는 64KB를 넘는 stdout을 받는 호출에서 promise가 영영
settle하지 않을 수 있다(업스트림은 1.4.0에서 수정). #74·#77이 `showBytes`와
`summary.ts`를 `gitOutput.ts` 헬퍼로 옮긴 뒤에도 큰 출력을 받는 `$`가 넷
남아 있었다:

- 지문의 `status --porcelain -z -uall` — watch가 2초마다 부른다
- `getDiffFiles`의 `diff --name-status` (큰 diff)
- `getDiffFiles`의 `ls-files --others` (untracked 켜짐)
- `refs.ts`의 `for-each-ref` (원격 브랜치가 많은 리포)

넷 다 `gitText`로 옮긴다. 이로써 서버에 남은 `$`는 `rev-parse`·
`merge-base`·`gh pr view`·`worktree list`처럼 출력이 작은 호출뿐이다.
자가 치유 3부품(flight 타임아웃·503·재시도)은 원인을 가리지 않는
안전망이라 그대로 둔다.

회귀망 `git-large-output.test.ts`는 호출처마다 그 함수를 8번 동시에 불러
출력을 겹치게 만든다(동시 호출은 실제로 일어난다 — 선택이 다른 요청,
prewarm, watch 폴). Bun 1.3.12에서 수정 전 코드는 4/4 실패, 수정 후 4/4
통과. 호출을 하나씩 되돌리는 뮤테이션으로 판별력을 쟀다: 지문·
name-status·ls-files는 각 3/3 잡히지만 for-each-ref는 좁은 구간에서만
확률적으로 멈춰 20라운드로도 5번 중 3번만 잡힌다 — 테스트와 CLAUDE.md에
그대로 적었다. 행업 단언은 1.3.x에서만 갈리므로 CI `test-bun13` 잡에 이
파일을 더한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK
- for-each-ref 케이스는 8-way에선 되돌린 코드를 20라운드로도 5번 중 3번만
  잡았다. 그 케이스만 16-way로 올리자 첫 라운드에 11번 중 11번 잡는다
  (1.3.12, macOS — 리뷰어 6/6 + 재현 5/5). 라운드는 20 → 5.
- beforeAll에 60초 상한을 준다. 픽스처가 평소 1.2~1.8초인데 부하가 걸린
  뮤테이션 실행에서 훅 기본 상한 5초를 넘어 무관한 이유로 실패한 것을
  실측했다 — CI 러너에서도 날 수 있는 결함이다.
- 테스트 상한을 라운드 수에 맞춰 케이스별로 계산한다.
- refs.ts의 worktree list도 gitText로 옮긴다. 출력이 등록된 워크트리 수에
  비례하고, 디렉토리가 지워져도 prunable로 등록이 남아 약 300개면 64KB를
  넘는다. 다만 죽은 워크트리 400개(110KB)로 `$`를 8·16-way 10라운드씩
  8번 돌려도 한 번도 안 멈춰 판별할 모양을 못 찾았으므로 회귀망은 두지
  않고 그 사실을 적는다. 크기는 필요조건일 뿐이라는 것도 적는다.
- 남은 `$`는 rev-parse·merge-base·gh pr view — 출력 크기가 리포 규모와
  무관한 호출뿐이다.
- CI 잡 주석의 "10초"(새 파일은 15초), git-output.test.ts docblock의
  오해 소지 있는 문장을 고친다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK
Linux 러너에서 되돌린 코드로 test-bun13이 빨개지는 것을 확인했으므로 되돌린다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAe9zh6siLHSR4nPpqm8QK
@say8425
say8425 merged commit 2802806 into main Sep 21, 2026
16 of 18 checks passed
@say8425
say8425 deleted the fix/remaining-git-spawn branch September 21, 2026 08:05
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