예전에 AstroPaper 테마에 PR을 하나 낸 적이 있는데, 그건 직접 GitHub 웹에서 손으로 했다. 이번엔 방식을 바꿔보기로 했다 — GitHub 토큰을 주고, fork부터 커밋, push, PR 오픈까지 전부 명령어로 시켜봤다. 오늘 하루 동안 Honcho 셀프호스팅 작업을 하면서 PR 감이 세 개나 나왔길래, 그걸로 시험해봤다.
세 번 다 같은 곳에서 막혔다
첫 PR(elkimek/honcho-self-hosted, config.toml 스키마 drift 수정)을 시작하려고 fork를 시도했는데 바로 막혔다.
failed to fork: HTTP 403: Resource not accessible by personal access token
원인은 토큰 종류였다. GitHub의 fine-grained PAT(요즘 권장되는, 리포별로 권한을 세밀하게 지정하는 방식)는 fork API를 완전히 지원하지 못한다. classic PAT(repo scope 하나로 뭉뚱그려진 예전 방식)가 필요했다. classic 토큰을 받아서 export GH_TOKEN=...으로 세션에 주입하니 fork는 됐는데, 이번엔 git push가 막혔다 — could not read Username for 'https://github.com'. GH_TOKEN 환경변수만으론 git 자체의 인증 정보까지는 안 엮이는 거였다. gh auth setup-git을 한 번 더 해줘야 git이 그 토큰을 credential helper로 쓰게 된다.
이 조합(classic PAT + GH_TOKEN + gh auth setup-git)을 알고 나니 첫 PR은 무사히 열렸다.
그런데 세 번째 PR을 열 때 완전히 똑같은 403이 다시 났다. 세션이 바뀌면서 gh auth status가 보여주는 기본 토큰이 다시 fine-grained로 돌아가 있었던 거다. classic 토큰은 세션 안에서만 임시로 주입했던 거라 저장이 안 됐고, 새 세션이 시작되니 fine-grained 토큰으로 원상복구된 상태였다. 결국 다시 물어봐야 했다.
fork에 필요한 권한이 지금 설정된 토큰(fine-grained)엔 없어서 막혔어 — 지난번 PR #1 때처럼 classic PAT(
reposcope)가 필요해. 그때 준 토큰을 다시 줄 수 있어?
한 번 겪었다고 안 겪는 게 아니라, 세션이 바뀔 때마다 원점으로 돌아가는 종류의 문제였다.
토큰을 받을 때마다 확인한 것
토큰을 붙여넣을 때마다 형식부터 확인했다. classic PAT는 ghp_ + 36자, 총 40자여야 한다.
TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
echo "length: ${#TOKEN}" # 40이어야 정상
echo "$TOKEN" | grep -c "ghp_" # 1이어야 정상 (중복 붙여넣기 감지)
지난 PR 때는 실제로 토큰이 두 번 겹쳐 붙여넣기된 적이 있었다(ghp_...ghp_...가 구분자 없이 이어 붙은 형태). 길이/prefix 개수를 먼저 확인하는 습관을 들여두니, 이번엔 바로 정상 토큰임을 확인하고 넘어갈 수 있었다. 붙여넣고 바로 쓰지 않고, gh api user로 실제 인증이 되는지 확인한 다음에야 진행했다.
세 PR이 실제로 고친 것
- PR #1 —
elkimek/honcho-self-hosted. upstream Honcho가 config 스키마를 바꾸면서 이 커뮤니티 템플릿이 낡아있던 걸 발견하고 고침. 실제 pydantic 스키마(AppSettings)에 직접 대조 검증하면서 진행했고, 덤으로docker-compose.yml의 API 포트가 공개 노출돼 있던 것과setup.sh의 embedding URL이 clobber되는 버그도 같이 잡았다. - PR #2 —
plastic-labs/honcho. pgvector의 HNSW 인덱스가 2000차원 하드 리밋을 가진다는 걸 문서 어디에도 경고하지 않고 있었다. Gemini의gemini-embedding-001은 기본 3072차원을 뱉는데, 이걸 그대로 쓰면 인덱스 생성 시점에야 실패한다.config.toml.example에 경고 주석을 달고, README의 config 섹션 목록에서[embedding]항목이 통째로 빠져있던 것도 같이 고쳤다. - PR #3 —
NousResearch/hermes-agent. 메모리 프로바이더 플러그인(예:honcho.json)이 자기 설정 파일은 완벽히 채워져 있어도,config.yaml의memory.provider에 이름을 안 넣으면 조용히 비활성 상태로 남는다.hermes memory status가 이걸 전혀 알려주지 않길래, 비활성 상태인 프로바이더 중 실제로 설정이 다 돼 있는 걸 감지해서 경고를 띄우는 로직을 추가했다. 특정 프로바이더 전용 로직이 아니라, 모든 프로바이더가 공유하는is_available()패턴을 그대로 활용해서 범용으로 짰다.
세 PR 다 실제 pydantic 스키마 검증, 기존 테스트 스위트 실행, ruff/markdownlint 통과까지 확인하고 나서 열었다. 그리고 매번 여는 순서는 같았다 — fork → clone → 브랜치 → 수정 → 검증 → 커밋 → push → PR 제목/본문 초안을 보여주고 승인받은 뒤에야 오픈.
열고 나서도 안 끝났다
PR #2를 연 지 얼마 안 돼서 CodeRabbit(자동 리뷰 봇)이 코멘트를 남겼다. “actionable 코멘트 1개” — 내가 쓴 주석 중 “2000차원을 넘으면 pgvector가 인덱스 생성 시점에서만 거부한다”는 표현이 정확하지 않다는 지적이었다.
확인해보니 맞는 말이었다. 리포 안에 src/startup/embedding_validator.py라는, 부팅 시점에 기존 pgvector 컬럼과 VECTOR_DIMENSIONS 설정값이 일치하는지 검사하는 모듈이 따로 있었다 — 처음 조사할 때는 못 봤던 파일이다. 상황에 따라 pgvector의 인덱스 생성 시점보다 더 일찍, 부팅 단계에서 먼저 걸릴 수도 있었던 거다. 봇의 지적을 그대로 받아들이거나 무시하지 않고, 직접 그 파일을 열어서 정말 그런 검증을 하는지 확인한 다음 주석을 고쳐서 같은 브랜치에 커밋을 하나 더 push했다.
리뷰 봇이 남긴 코멘트라고 무조건 믿거나 무시하지 않고, 실제 코드를 열어서 근거를 확인한 뒤에 반영하는 흐름은 사람 리뷰어의 코멘트를 처리할 때와 다를 게 없었다.
남는 생각
명령어로 하니 반복 작업(fork, clone, 브랜치 생성, 커밋 메시지 작성)은 확실히 빨랐다. 근데 그 빠름의 발목을 잡은 건 매번 똑같은 지점 — GitHub 토큰 권한 문제였다. 세 번 다 같은 원인, 같은 해결책이었는데도 세션이 바뀔 때마다 처음부터 다시 겪었다. 자동화가 아무리 잘 돼 있어도, 인증처럼 세션 경계를 못 넘는 상태는 매번 사람이 다시 채워줘야 한다는 걸 새삼 확인했다.