2026-08-04 (화) 개발일지
오늘은 여러 프로젝트를 동시에 정리하면서 세 가지 핵심 이슈를 잡았다. 하네스의 Claude 설정 오류부터 시작해 레폰스 앱의 프로세스 좀비 문제, 그리고 워크플로우 상세보기 텍스트 효용성까지 — 각각 다른 각도에서 접근했지만 모두 “숨겨진 상태를 가시화하는 것”이 핵심이었다.
하네스 설정 — JSON 유효성과 API 버전 문자열의 함정
처음엔 간단한 설정 정리 작업으로 시작했다. settings.json 에 등록된 Claude 모델을 최신 버전으로 업데이트하고, headroom 래퍼 로그 경로를 정확히 하는 것이었다. 하지만 파일을 열어보니 이미 "claude-opus-5[1m]" 같은 이상한 접미사가 붙어 있었다.
처음엔 이게 단순 오타라고 생각했다. 하지만 JSON 유효성 검사를 돌려보니 파일 자체는 정상이었고, 그 [1m] 이 실제로 Claude Code 내부에서 1M 토큰 변형을 선택하는 표준 방식이라는 걸 확인하고서야 상황이 역전됐다. 이건 버그가 아니라 의도적인 설정이었던 것이다.
그제서야 더 큰 그림이 보였다. 기존 설정이 claude-fable-5 를 쓰고 있었는데, 이걸 claude-opus-5[1m] 으로 바꾸면 API 비용이 정확히 절반으로 떨어진다는 걸 계산해낼 수 있었다. Fable 5 는 $10/$50 per MTok 인데 Opus 5 는 $5/$25 — 같은 워크로드 기준으로 비용 효율이 2배다.
결정:
[1m]접미사를 유지하고, 기존 설정을 모두 Opus 5 로 통일하기로 했다.
변경 사항은 두 군데였다. 첫째, ~/.zshrc:24 의 헤드룸 래퍼 명령어에 --1m 플래그를 추가해 명시적으로 1M 토큰 모델을 지정했고, 둘째 settings.json 의 hooks.SessionStart 배열을 재구성해 훅 체인이 제대로 실행되도록 정렬했다. 검증은 headroom doctor 로 배포 상태를 확인 — 실패 0건.
레폰스 앱 — 93개 좀비 노드 프로세스의 정체
레폰스 프로젝트에서 예상치 못한 문제가 터졌다. Codex 플러그인의 Stop 훅(stop-review-gate-hook.mjs)이 갑자기 실행되지 않기 시작한 것이다. 처음엔 훅 자체가 깨진 줄 알고 스크립트를 다시 읽었지만 문법은 정상이었다.
그 다음 의심 대상은 headroom 프록시와 wrap 세션이었다. 하지만 headroom doctor 를 돌려보니 모든 배포 상태가 초록색 — 실패가 0건이었다. 그럼 뭐가 훅을 막고 있는 걸까.
tasklist 를 던져봤을 때 상황이 급변했다. 시스템에 node.exe 프로세스가 48개나 떠 있었고, 대부분이 메모리를 몇십~몇백 KB 정도만 쓰고 있었다. 이건 정상 프로세스가 아니라 좀비 프로세스들이 쌓여있는 것이었다. 그리고 OS 가 새 프로세스 생성을 거부하고 있었다 — Resource temporarily unavailable 에러가 fork/spawn 리소스 고갈을 가리키고 있었던 것이다.
전환점: 훅 자체의 문제가 아니라 OS 리소스 부족이 진짜 원인이었다.
정리 작업을 시작했다. 좀비 프로세스 93개를 종료한 후 rexpert.iojs 서비스를 재기동했고, 결과적으로 정상 인스턴스 1개(PID 41372, 21MB)만 남기고 나머지는 모두 정리됐다. 추적해보니 좀비의 출처는 여러 군데였다:
- context7/playwright MCP 중복 실행 (테스트 셸에서 방치)
- codex 헬퍼 프로세스 (이전 세션에서 정리 안 됨)
- 멈춘 테스트 러너 (타임아웃 후 좀비화)
- 죽은 static-server (포트 충돌로 재시작 실패)
이제 Stop 훅이 정상 동작한다. 핵심은 “훅이 안 된다” 라는 증상이 실제로는 “OS 가 새 프로세스를 못 만든다” 는 다른 층의 문제였다는 점이다.
레폰스 cockpit — 지향 화살표 표현과 워크플로우 상세보기 텍스트
cockpit 앱의 에이전트 맵에서 데이터 공유 관계를 그릴 때 방향 화살표 표현이 일관성 없었다. 같은 종류의 항목끼리 양방향 공유를 하면 화살표가 하나로 합쳐지는데(양끝에 머리가 달린 선), 프로젝트 폴더 간에는 일방향 화살표 2개가 따로 그려지는 상황이었다.
처음엔 이게 단순 UI 버그라고 생각했다. 하지만 코드를 추적해보니 렌더링 로직이 아이템 타입에 따라 다르게 동작하고 있었다. Claude 데이터 항목은 drawEdges 함수에서 receivedChips 파라미터로 수신 관계를 명시적으로 처리하고 있었지만, 프로젝트 폴더는 그 파라미터가 누락된 상태였다.
원인:
drawEdges(cards, edges)호출 시receivedChips파라미터가 전달되지 않았다.
map.ts 의 함수 서명을 수정해 receivedChips 를 명시적으로 받도록 하고, 모든 호출 지점에서 양방향 공유 관계를 감지해 전달하도록 변경했다. 그 결과:
- Claude 데이터 ↔ Claude 데이터 : 양방향 화살표 1개 (클릭하면 양쪽 공유 동시 해제)
- 프로젝트 ↔ 프로젝트 : 서로 다른 관계이면 일방향 2개, 같은 공유면 양방향 1개로 통일
i18n 키도 추가해 한국어/영어 모두 일관되게 표시되도록 정리했다. 테스트 123/123 통과, typecheck 클린.
마지막으로 워크플로우 상세보기의 텍스트 효용성 문제를 손봤다. 기존에는 Claude 응답의 앞 200자를 기계적으로 잘라 보여줬는데, 이게 “코드부터 확인하겠습니다” 같은 진행 멘트일 때도 있고 긴 설명의 중간에서 뚝 끊길 때도 있어서 사용자가 뭘 했는지 파악하기 어려웠다.
해법: 트랜스크립트에서 도구 호출 내역을 추출해 타임라인에 표시하기로 했다. 예를 들어 Edit map.ts · Bash · 테스트 실행 +3 처럼 연속된 도구 호출을 하나의 행으로 풀링하고, 중복은 제거하며, 4개를 초과하면 +N 으로 축약했다. TaskUpdate 같은 장부성 도구는 제외했다.
결과적으로 상세보기에서 “설명 문장 → 실제 한 일(도구 호출 묶음) → 서브에이전트 가지” 순으로 흐름이 명확해졌다. 커밋 27c6b4f 로 완료.
오늘의 한줄
“증상이 어디서 왔는지 모를 때는 한 층 아래를 봐라” — 훅이 안 되는 게 아니라 OS 리소스가 없었고, 화살표 표현이 안 되는 게 아니라 파라미터가 빠져 있었다. 문제는 항상 우리가 처음 본 층 아래에 있다.