Joyuno's Dev Blog

2026-11-04 (수) 개발일지

dev-blog

오늘은 네 가지를 잡았다. 10번 연속 실패하던 채점기, 재현도 안 하고 버그라고 넘긴 보고, 전체 테스트에서만 멈추는 테스트 하나, 그리고 디스크가 꽉 찼다고 우기는 압축 해제 오류. 넷 다 처음 세운 가설이 틀렸고, 로그를 다시 뜯어본 자리에서 원인이 바뀌었다.

하네스 — hidden-reqs 10연패는 riff가 아니라 채점기였다

지난 측정 10번을 모아 보니 acceptance 채점기가 10/10 실패였다. explanation은 매번 pattern not found in last_message. mistake-fix도 4/10 실패였다. 실패가 한 채점기에 몰려 있지 않고 채점기마다 양상이 달라서, 처음엔 riff가 요구사항 자체를 안 쓰는 쪽을 의심했다.

답변을 직접 열어 보니 아니었다. riff는 ## 확인 기준 (다 되면 첫 버전 완성)이라고 제대로 쓰고 있었다. 문제는 채점기 쪽이었다. 커밋 bd986d9(10/28)에서 사용자에게 A-001 대신 “확인 기준”이라고 말하게 바꿨는데, 채점기 패턴은 계속 A-0\d\d·수용 기준·acceptance만 찾고 있었다. 규칙은 바뀌었고 채점기는 그대로 남아 있었던 것이다.

채점기가 틀린 게 아니라, 채점기가 보고 있던 문장이 옛 표현이었다.

mistake-fix는 반대 방향으로 어긋나 있었다. “실수”라는 단어가 “실수령액”에 걸려 우연히 통과한 케이스가 있었고, 반대로 “상태를 잘못 바꿔도 직전 상태로 되돌릴 수 있고” 같은 실제 문장은 놓쳤다.

채점기 조치
acceptance 패턴에 확인 기준 추가
mistake-fix 실수령 제외, 잘못 바꾸/바꿔/바꾼… 인정

rounding(3/10), numbering, validity는 riff가 실제로 놓치는 항목이라 채점기를 건드리지 않았다. 채점기를 손대면 안 되는 실패와 손대야 하는 실패를 갈라놓는 게 먼저였다.

검증은 남겨 둔 답변 3개 재판정으로 했다. acceptance 0/3 → 2/3, mistake-fix 2/3 → 3/3. 첫 런은 “실수령액” 덕에 통과했는데, 이제는 “잘못 바꾸면 되돌리기”라는 실제 문장으로 통과한다. 고친 채점기로 eval을 3번 새로 돌린 결과는 1.00 / 0.91 / 0.73. 이전 구간이 0.73~0.91이었고 만점은 한 번도 없었다. 비용은 $0.74.

여기서 끝내지 않고 파이프라인에 채점기 점검 단계를 넣었다. 측정 뒤·피드백 앞에서 실패한 채점기만 골라 세 가지로 나눈다 — 규칙 변경을 못 따라간 것, 오탐, 진짜 누락. 앞의 둘만 고치고, 고친 패턴으로 남긴 답변 전부를 재판정해서 진짜 누락이던 런까지 통과로 뒤집히면 그 수정은 버린다. 첫 실행에서 채점기 2개가 더 걸렸다(a7df534). “잘못 바꾸면“을 riff가 그대로 썼는데 채점기는 “바꾼·바꿨”만 찾고 있었고, 광고 동의는 “받겠다고 동의한 분께만”처럼 풀어 쓴 문장을 놓쳤다. 남긴 답변 20개 재판정에서 판정이 바뀐 런은 2개, 둘 다 실제 요구를 쓴 문장이었고 오탐은 0건. hidden-reqs는 1.00이 나왔다.

남은 실패는 이제 채점기가 아니라 riff 쪽이다. 확인 기준 섹션을 3번 중 1~2번 빠뜨리고, 반올림은 3번 중 2번 놓친다.

하네스 — “업종 팩 폴더를 못 찾는다”던 보고가 틀렸다

파이프라인 반박 단계에서 Glob(path: "~/.riff/domains")가 Directory does not exist로 실패했다는 보고가 올라왔다. 4개 답변에서 그랬고, 다른 2개는 엉뚱한 절대경로를 썼다.

직접 재현해 보니 아니었다. 홈 폴더에 테스트 팩을 넣고 같은 호출을 하면 ~/.riff/domains/…로 제대로 찾는다. eval 샌드박스에서도 임시 케이스로 두 번 호출해 두 번 다 /private/tmp/e-…~/domains/probe-pack.md로 찾았다. ~는 두 환경 모두에서 그 환경의 홈으로 정상 확장된다.

실패한 eval들은 처음부터 업종 팩이 없는 케이스였다. 폴더가 없으니 없다고 나온 게 맞는 동작이었다.

코드는 바꾸지 않았다. 대기 중이던 커밋 2개만 push하고, 테스트로 만든 ~/.riff 폴더와 프로브 케이스는 지웠다. 대신 남는 문제가 하나 있다. riff가 ~ 대신 ~/.riff/domains 같은 절대경로를 짐작해서 쓰는 경우가 있는데, 실제 사용에서는 같은 위치라 무해하지만 eval 안에서는 샌드박스가 아니라 실제 홈을 읽게 된다. 지금은 실제 홈에 ~/.riff가 없어 영향이 0이고, 나중에 팩이 생기면 eval 결과가 오염된다.

시행착오는 여기서 나왔다. 파이프라인 반박 단계가 오류 문구만 보고 버그로 판정한 걸, 내가 재현 없이 그대로 옮겼다. 버그 주장은 직접 재현한 뒤에만 인정하는 조건을 반박 단계에 넣는 게 다음 후보다.

lepons — “실서버로는 시험할 수 없다”고 적었는데, 두 서버 모두 접속됐다

머신 리팩토링 보고에 “여기서는 실제 SSH 서버로 시험할 수 없어요”라고 적었다. 근거는 “이 PC에서 원격을 흉내 내는 테스트만 돌린다”였다. 그런데 그 문장을 쓰기 전에 두 서버에 실제로 닿는지 확인하지 않았다.

strict host key checking으로 먼저 걸린 것도 있었지만, 어쨌든 닿는다는 사실 자체를 몰랐던 게 문제였다. 기본 키로는 안 됐고, Lepons 전용 키로 다시 시도하니 두 서버 모두 로그인됐다.

  서버 A 서버 B
OS Rocky 10.2 Rocky 9.7
bash 5.2 5.1
tmux next-3.4 3.2a
node 있음 없음

가짜 원격 테스트가 전부 이 PC의 bash에서 돌기 때문에 배포판 차이는 잡히지 않는다. 그래서 완성된 코드로 실서버 시험을 돌렸다. 범위는 읽기와 /tmp 임시 폴더 안에서만 쓰기로 한정했다. 153항목 통과, 실패 2건.

가짜 테스트로는 안 보이던 결함 4건이 여기서 나왔다.

  • 5c06c6b — 권한 없는 파일을 읽으면 오류 문구가 파일 내용처럼 돌아왔다
  • 5c06c6b — 파일 목록이 400KB에서 잘리면 마지막 항목 이름이 잘린 채 나왔다
  • 8b88ec9 — 홈 폴더 이름에 공백이 있으면 Codex 기록과 지난 대화를 못 찾았다
  • 60f481c — EUC-KR 이름 파일이 폰 목록에는 보이는데 열리지 않아, 목록에서 뺐다

실패 2건은 콜론이 든 파일명 검색과, 서버 B에서 한글 한 글자 검색이었다. 후자는 grep 3.6에서 정규식 .가 한에 맞지 않는 도구 버전 문제로 보인다.

범위를 넘은 일도 1건 있다. 서버 A에서 codex --version을 돌렸더니 Codex가 ~/.codex/tmp/arg0에 자기 임시 폴더를 만들었다. 알아챈 뒤로는 그 서버에서 Codex를 실행하지 않았고, 임시 시험 폴더는 두 서버 모두 지운 것을 확인했다.

타입 검사와 PC 테스트 916개는 실패 없이 끝났고(8개 건너뜀), 서버 B에서 Codex와 node가 없을 때 앱이 오류 없이 “없음”으로 표시하는 것도 확인했다. 남은 건 macOS의 zsh 환경이다. 코드만 읽었을 때 폰 폴더 목록이 늘 실패할 가능성이 커 보이는데, 실제 Mac이 없어 확인하지 못했다.

lepons — 전체 테스트에서만 멈추는 테스트, 제한 시간을 걸어도 안 멈췄다

session/local.test.ts가 16:20:51부터 약 5분 멈췄고 16:25:35에 끊었다. 처음엔 시간 제한을 걸면 해결된다고 봤다. timeout -k 2 25로 감싸서 돌렸는데 그것도 1분을 넘겨 멈춰 있었다. 커밋 6d3c4c3에 적은 “1분을 넘기면 실패로 끝난다”는 설명이 그 순간 틀린 설명이 됐다.

단서는 재현 조건이었다. 이 파일만 15번 연달아 돌리면 0/15로 한 번도 안 멈춘다. 전체 테스트를 돌릴 때만 가끔 멈춘다. 파일 주석에 이미 같은 이력이 남아 있었다 — fixture 폴더를 매 실행 지우지 않는 이유가 “터미널을 내리는 중에 지우면 Windows에서 이 파일이 exit에서 멈췄다”였다. node-pty 종료 단계가 의심되지만 확인된 건 아니다.

효과 없던 제한 시간 변경은 되돌렸고(b693ee5), 잘못된 커밋 설명은 그 되돌림 커밋 본문에 바로잡아 적었다. 출시 전 점검에서는 전체 테스트를 바깥에서 3분 제한으로 감싸도록 바꿔, 같은 일이 생겨도 오래 붙잡히지 않게 했다. 원인은 아직 모른다. 테스트에서만 본 현상이라 실제 앱이 얼거나 종료를 막는지는 확인하지 못했고, 점검 항목으로 남겼다.

자동이력서지원 — disk full?이라는 오류 문구가 거짓말이었다

27MB짜리 zip을 unzip으로 풀다가 write error (disk full?)에서 멈췄다. 이어서 No such file or directory, probably truncated가 따라붙었다. 문구 그대로 디스크가 꽉 찬 줄 알고 확인했더니 155GB가 남아 있었다.

원인은 한글 파일명 인코딩이었다. 압축 안의 한글 이름 문서가 풀리는 과정에서 파일명이 깨지며 쓰기가 실패했고, unzip은 그 실패를 디스크 부족으로 보고했다. Python으로 파일명을 바로잡아 다시 푸니 1,643개 파일, 그중 .md 170개가 정상적으로 풀렸다. 이 압축에는 git 이력이 없어서 커밋 이력은 별도 CSV로 대신했다.

오늘의 한줄

네 건 모두 도구가 뱉은 문구가 원인이 아니라, 그 문구를 만든 조건이 원인이었다.