Joyuno's Dev Blog

2026-10-22 (목) 개발일지

dev-blog

오늘은 두 가지 이슈를 정리했다. 하나는 Lepons Cockpit의 macOS Finder 실행 환경에서 tailscale 명령이 먹통이 되는 버그였고, 다른 하나는 자소서 검수 파이프라인이 GPT 토큰을 예상보다 15배 빨리 소진하던 문제였다. 둘 다 원인 추적 과정에서 처음 가설이 틀렸음을 확인하고 방향을 꺾은 케이스다.

Lepons Cockpit — Finder 실행 환경에서 tailscale CLI 모드 진입 실패

증상 Lepons Cockpit(Electron 앱)을 macOS에서 Finder로 더블클릭해 실행하면, 앱 안에서 tailscale status 같은 명령이 실패한다. 표준 출력은 비어 있고 표준 에러에 “error fetching status” 문장을 찍는다. 터미널에서 npm start로 띄우면 전혀 문제가 없다. 실행 방식에만 의존하는 재현 조건이었다.

단서 수집 + 첫 가설 처음엔 PATH 문제를 의심했다. Finder로 실행한 앱은 launchd가 물려주는 최소 PATH만 가지는데, tailscale 바이너리가 표준 경로에 없을 거라 생각했다. 하지만 로그를 찍어보니 PATH에 /usr/local/bin이 정상 포함되어 있었다. 바이너리 자체는 찾을 수 있는데도 명령이 실패하고 있었다.

원인 추적 — TERM 하나로 GUI/CLI 모드가 갈림 PATH가 문제가 아니라면 환경변수 차이를 봐야 했다. 터미널과 Finder 실행 환경의 env를 비교했다. 터미널에는 TERM=xterm-256color, SHLVL=1이 있지만 Finder 실행 환경에는 둘 다 없었다. env -i로 깨끗한 환경을 만들어 tailscale status를 실행해보니 증상이 재현됐다. 그런데 여기서 이상한 점 — exit 0을 반환하면서 실패했다. 일반적인 CLI 실패라면 0이 아닌 종료 코드를 반환해야 정상이다.

tailscale status가 exit 0을 반환하면서 실패한다 — 이 행동이 힌트였다. 바이너리가 CLI 모드가 아니라 GUI 모드로 진입하고 있다는 신호였다.

macOS tailscale 바이너리는 GUI와 CLI를 겸용한다. TERM/SHLVL이 없으면 “터미널이 아니다”고 판단해 GUI 모드로 진입하고, GUI 모드에서는 CLI 인자를 무시한 채 에러 문장만 찍고 조용히 종료하는 것이었다.

해결 — TAILSCALE_BE_CLI 플래그로 전환 처음엔 TERM 유무를 검사하는 휴리스틱을 넣으려 했다. 하지만 조사 중 tailscale 바이너리에 TAILSCALE_BE_CLI라는 환경변수 플래그가 존재한다는 걸 알게 됐다. 이걸 설정하면 환경이 완전히 비어 있어도 CLI 모드로 강제된다. TERM 휴리스틱은 조건이 까다롭고 미래에 깨질 가능성이 있지만, TAILSCALE_BE_CLI는 바이너리에서 공식 지원하는 플래그라 안정적이다.

수정은 src/phone/tailscale.ts에서 tailscale() 함수 호출 시 TAILSCALE_BE_CLI=1을 환경변수로 설정하는 방식이다. tailscale.test.ts도 함께 수정했다.

항목 before after
CLI 모드 판정 TERM / SHLVL 존재 여부로 추정 TAILSCALE_BE_CLI=1 명시적 설정
Finder 실행 환경 실패 (GUI 모드 진입) 정상 동작
빈 환경 (env -i) 실패 정상 동작

검증 + 릴리스 테스트 426/426 통과, 타입체크 통과. macOS에서 실제 Finder 실행으로 동작 확인 후 버전을 1.7.2로 올려 릴리스했다. verify-lepons-cockpit-on-macos.md에 재현 절차를 문서화해 다음에 같은 환경 버그가 나왔을 때 바로 테스트할 수 있게 했다.

자소서 검수 파이프라인 — Codex 3회 검수가 토큰을 15배 과소비한 이유

증상 자소서 검수 작업을 몇 건 진행했을 뿐인데 GPT 일일 크레딧이 동났다. 문항 수 대비 토큰 사용량이 예측을 크게 벗어나고 있었다.

단서 수집 + 첫 가설 처음엔 Aside나 Codex MCP 플러그인이 몰래 GPT를 호출하는 게 아닌지 의심했다. 로그를 전부 뒤졌다. Aside는 기본 모델이 claude-code / claude-opus-5-5로 설정되어 GPT 호출 기록이 0건이었고, Codex MCP도 호출 기록이 전혀 없었다. 진짜 원인은 src/jobapply/review.py의 검수 파이프라인이었다.

원인 추적 — file read 오버헤드 + 3회 루프 파이프라인을 뜯어보니 문항 하나당 검수가 3회씩 돌고 있었다. codex가 자소서 본문을 읽고 수정한 뒤, 그 수정본을 다시 codex에 넣어 또 검수하는 식이었다. 문제는 codex가 매번 파일을 열어 읽는다는 점이다. codex 호출 한 번에 입력 약 30만 토큰이었고, 3회면 문항 하나