Etc

설정은 고쳤는데 왜 안 먹지? — 프로세스 버전과 파일 버전은 다른 값이다

Collie 페어링 오류를 herdr 버전 문제로 오진한 뒤, 설치 파일과 실행 중인 서버 버전을 구분하는 규칙을 남겼다

한 줄로

어제 “승인되지 않은 기기” 오류의 원인을 CLI 도구의 버전 불일치로 지목했다가 틀렸다. 진짜 원인은 전혀 다른 시스템의 기기 페어링 게이트였고, 로그에 그 시스템 이름이 0건 찍혀 있던 것이 이미 신호였는데 늦게 봤다. 이 오진에서 건진 한 줄을 dotfiles 규칙으로 남겼다 — “바이너리 버전”과 “실행 중인 프로세스 버전”은 다른 값이다.

기기 페어링 게이트란?

쓰기 요청을 막는 게이트 중에는 환경변수로 켜는 것이 아니라 상태로 켜지는 것이 있다. 어제 걸린 게이트가 그랬다. 문서에는 이렇게 적혀 있었다.

DEVICE PAIRING — a second, independent write gate… On exactly while at least one device is paired.

즉 기기가 하나라도 페어링되면 자동으로 켜지고, 켜진 뒤로는 등록되지 않은 기기의 요청이 전부 거부된다. 켜고 끄는 스위치가 설정 파일 어디에도 없으니, 설정만 들여다보는 조사에서는 존재 자체가 안 보인다.

같은 시스템의 다른 게이트 두 개(COLLIE_DEVICE_HEADER, COLLIE_TRUSTED_USER)는 모두 미설정이었다. 확인한 다른 두 게이트가 미설정이라 기기 페어링 게이트를 원인으로 좁혔다.

조치는 단순하다. 페어링 코드를 발급해서 해당 기기에서 직접 입력한다. 설정 화면의 “Pairing code” 칸에 코드를, “Name for this device” 에 이름을 넣는다. 코드는 10분 만료·1회용이라, 입력하러 가는 사이에 만료되면 재발급해야 한다. 마지막 입력만큼은 원격으로 대신 눌러줄 수 없다 — 그 기기를 등록하는 행위이기 때문이다.

오진: 버전 불일치를 범인으로 지목했다

처음 지목한 원인은 CLI 도구의 0.9.0 → 0.9.1 버전 불일치였다. 근거는 이랬다.

  • 로컬 바이너리가 0.9.0 (9/8 설치 후 그대로)
  • update.available version="0.9.1" 감지 1회 (9/16 18:58)
  • update.apply / download / install 로그 0건 — 감지만 하고 적용은 안 됨
  • 서버 프로세스 PID 78531 이 9/8부터 11일 연속 실행 중

여기에 릴리스 노트의 이 문장이 결정타처럼 보였다.

Update Herdr on both machines to use CLI forwarding; failed remote commands never fall back to Local.

원격 명령이 실패한다는 증상, 버전을 맞추라는 릴리스 노트, 실제로 안 맞는 버전. 정황이 전부 한 방향을 가리켰다.

그런데 증상과 무관했다. 페어링 게이트를 가진 시스템의 존재 자체를 몰랐던 것이 이유다.

늦게 본 신호: 로그에 0건이라는 사실

되돌아보면 신호는 이미 있었다. CLI 도구의 로그에 문제의 시스템 이름이 0건이었다. 두 시스템이 별개라는 뜻이다. 한쪽 로그에 다른 쪽 이름이 한 번도 안 나온다면, 한쪽을 고쳐서 다른 쪽 증상이 사라질 이유가 없다.

“0건”은 화면에 아무것도 안 뜨기 때문에 읽고도 지나치기 쉽다. 반대로 “업데이트 감지 1회, 적용 0건” 같은 0건은 눈에 잘 띈다 — 내가 의심하고 있던 가설을 보강해주기 때문이다. 같은 0건인데 한쪽은 무기가 되고 한쪽은 안 보인다. 차이는 데이터가 아니라 그때 내가 무엇을 찾고 있었는가였다.

손해는 없었다. 업데이트와 서버 재시작 자체는 해롭지 않았고(11일 묵은 서버가 갱신됐다), 열려 있던 작업공간 8개는 모두 유휴 셸이라 잃은 작업이 없다. 하지만 원인을 못 찾은 시간은 그대로 날아갔다.

건진 규칙: 파일 버전과 프로세스 버전

이 조사에서 두 번 걸린 함정이 하나 있어 dotfiles 의 claude/CLAUDE.md 에 규칙으로 넣었다.

“바이너리 버전”과 “실행 중인 프로세스 버전”은 다른 값이다.
설정 수정 시각이 프로세스 시작 시각보다 나중이면 그 변경은 아직 테스트된 적이 없다.

디스크의 파일은 새 버전인데 메모리에서 도는 프로세스는 옛날에 뜬 그대로인 상황이다. 설치된 CLI가 새 버전이어도 이미 떠 있는 서버 프로세스는 옛 버전일 수 있다. 두 값이 어긋나 있는 동안은 “고쳤는데 안 먹는다”가 계속된다.

어제 두 번 다 이 모양이었다.

대상 어긋난 방식
터미널 에뮬레이터 창만 닫아 프로세스가 남아 있었음
맥미니의 CLI 도구 CLI 는 0.9.1, 서버 프로세스는 0.9.0

규칙을 “버전을 확인하라”가 아니라 “수정 시각과 프로세스 시작 시각을 비교하라”로 적은 게 핵심이다. 버전 문자열은 어느 쪽 것인지 헷갈리지만, 시각 비교는 헷갈릴 여지가 없다. 수정이 더 나중이면 아직 한 번도 안 돌아본 변경이다.

함께 적은 것 — 렌더링 문제는 파일만 봐서는 알 수 없으니 screencapture 로 화면을 직접 찍어봐야 하고, 멀티 디스플레이 환경이면 -D <n> 으로 화면을 지정해야 한다.

같은 커밋에 넣은 것들

커밋 2개로 정리했고 작업 트리는 깨끗하다. push 는 하지 않았다.

대상 내용
ghostty/config 폰트 폴백 11줄 — 심링크라 이미 반영돼 있었고 커밋만 안 된 상태였음
claude/CLAUDE.md “설정 고쳤는데 안 먹을 때 프로세스부터 의심한다” 규칙
claude/herdr-agent-quota.md 플러그인 함정 3가지 + 설치 순서 (신규)
herdr/config.toml + install.sh 설정 파일 편입, 심링크 블록 추가

첫 줄이 이 글의 주제를 한 번 더 보여준다. 폰트 설정 파일은 심링크(원본을 가리키는 바로가기 파일)로 이미 연결돼 있었고, 커밋만 안 된 상태였다. 파일 연결 상태와 커밋 여부는 별개였다.

설정 파일을 dotfiles 로 끌어올 때의 판단

herdr/config.toml 은 플러그인이 덮어쓸 위험이 있어 편입을 망설였다. 덮어쓰는 파일을 심링크로 걸면 원본이 날아간다.

근거로 삼은 건 관측이다 — 22시간 동안 파일이 한 번도 안 바뀌었다(전날 17:35 이후). 그 사이 에이전트 이벤트는 수없이 돌았다. 즉 플러그인은 configure 를 명시적으로 부를 때만 파일을 고친다. 이 근거를 install.sh 주석에 남겨, 나중에 “왜 이걸 심링크로 걸었지” 라고 물을 다음 사람(=미래의 나)이 다시 추론하지 않게 했다.

검증한 것 세 가지:

  • 문서에 적은 폰트 스캔 스크립트를 직접 실행 — D2Coding 충돌을 정확히 검출
  • 심링크가 configure 실행 후에도 유지됨 — 플러그인이 제자리 수정을 한다는 뜻
  • bash -n install.sh 문법 통과, 해당 도구 미설치 머신에서의 분기 확인

⚠️ push 할 때 주의할 것이 하나 남아 있다. 맥미니·맥북프로에 수동으로 넣은 설정이 있어서, push 후 그쪽에서 install.sh 를 돌리면 심링크로 전환되며 로컬 버전이 덮어쓰인다. 내용(rows_by_agent)은 같아 실질 차이는 없지만, “덮어쓴다”는 사실 자체는 알고 눌러야 한다.

정리

어제 배운 것을 순서대로 적으면 이렇다.

  1. 정황이 한 방향을 가리켜도 인과는 아니다. 버전 불일치·미적용 로그·릴리스 노트가 전부 같은 쪽을 가리켰지만 증상과 무관했다.
  2. 0건 로그를 읽어라. 가설을 보강하는 0건은 눈에 띄고, 가설을 깨는 0건은 안 보인다. 차이는 데이터가 아니라 내가 뭘 찾고 있었는가다.
  3. 설정 수정 시각과 프로세스 시작 시각을 비교하라. 수정이 더 나중이면 그 변경은 아직 테스트된 적이 없다.
  4. 오진은 규칙으로 남겨야 값을 한다. 같은 함정에 두 번 걸린 뒤에야 적었다.