매일 자동 실행되는 저장소에 코드 고쳐 넣기 — 봇이 커밋하는 파이프라인의 git 규율
매일 무인으로 도는 파이프라인 저장소는 봇이 몇 분 간격으로 커밋을 쏜다. working tree가 늘 dirty하고 내 커밋이 봇과 경쟁하는 이 저장소에 코드를 안전하게 이식하는 git 규율 — add -A 금지, pull --rebase --autostash, 짧게 끊기 — 과 경로에 #가 있어 스크립트가 잘리던 함정을 정리했다.
📌 시리즈 — 네이버 블로그 발행 자동화 (2편)
- 1편 · 네이버 스마트에디터 Playwright 자동화 — 조용히 틀어지는 함정 5가지
- 2편 · 매일 자동 실행되는 저장소에 코드 고쳐 넣기 (현재 글)
1편에서 만든 스마트에디터 자동화 스크립트를, 이미 매일 아침 무인으로 도는 다른 파이프라인에 코드로 옮겨 넣어야 했다. 그냥 파일 고치고 git add -A && git commit && git push 하면 될 줄 알았는데, 이 저장소는 그러면 안 되는 곳이었다.
이 저장소에는 봇이 살고 있다. 매일 정해진 시각에 실행되는 것과 별개로, 텔레그램 수신 봇이 데이터를 받을 때마다 run_logs/와 오프셋 파일을 갱신하고 몇 분 간격으로 log: ... 커밋을 자동으로 쏜다. 즉 내가 작업하는 동안에도 원격 main이 계속 앞으로 나간다.
살아있는 파이프라인 저장소에 손을 댈 때 지킨 규율과, 그 와중에 경로 하나 때문에 스크립트가 조용히 잘리던 함정을 정리한다.
문제 — working tree가 항상 dirty하고, main이 계속 움직인다
작업을 시작하려고 git status를 찍으면 내가 건드리지도 않은 파일이 이미 수정돼 있다.
1
2
M run_logs/bot.out
M run_logs/mj_bot_offset.json
봇이 방금 데이터를 처리하면서 남긴 산출물이다. 그리고 원격 로그를 보면 몇 분에 하나씩 커밋이 쌓인다.
여기서 평소 습관대로 하면 두 가지가 터진다.
git add -A— 내 코드 수정과 함께run_logs/·오프셋·bot.out같은 봇 산출물이 전부 딸려 들어간다. 심하면.env나 로그인 세션 폴더처럼 커밋하면 안 되는 것까지 휩쓸린다.git push— 내가 로컬에서 커밋하는 사이 봇이 원격을 먼저 밀어놨으면,push가 거부된다(non-fast-forward). 그냥 다시 밀려고pull하면 더티 트리 때문에 그것도 막힌다.
해결 — 라이브 저장소용 git 규율 네 가지
1. git add -A 금지 — 전파할 코드 파일만 지정한다
봇이 계속 건드리는 파일이 working tree에 떠 있으므로, 스테이징은 내가 실제로 바꾼 코드 파일만 이름으로 콕 집어서 한다.
1
2
3
4
5
# ✗ 이 저장소에서는 금지 — 봇 산출물이 딸려간다
git add -A
# ✓ 전파할 코드 파일만
git add scripts/naver_publisher.py
이건 단순히 “깔끔하게 커밋하자”가 아니라 사고 방지다. .env(토큰)와 로그인 세션 디렉터리는 .gitignore에 있지만, add -A는 상황에 따라 추적 중인 봇 파일을 함께 커밋 히스토리에 남긴다. 커밋 하나가 봇의 log: 흐름과 섞이면 나중에 “어느 커밋이 무엇을 바꿨나”를 못 가른다. 그래서 커밋 메시지도 봇의 log:와 구분되게 port: 같은 접두어를 붙였다.
2. 커밋 전 git pull --rebase --autostash
내 커밋을 봇 커밋 위에 얹어야 한다. --rebase는 병합 커밋 없이 내 작업을 원격 끝으로 옮겨주고, --autostash는 봇이 남긴 더티 트리를 자동으로 스태시했다가 rebase 후 되돌려준다.
1
2
3
git commit -m "port: ..."
git pull --rebase --autostash origin main # 봇 커밋 위로 rebase, 더티 트리는 자동 스태시
git push origin main
--autostash가 핵심이다. 이게 없으면 “working tree가 더러워서 rebase 못 한다”고 막히고, 매번 수동으로 stash → pull → pop을 해야 한다. 봇 파일은 내가 관여할 게 아니니 자동으로 넣었다 빼는 게 맞다.
3. push가 거부되면 다시 rebase하고 민다
pull --rebase와 push 사이의 짧은 순간에도 봇이 원격을 밀 수 있다. 그러면 push가 다시 거부된다. 당황할 것 없이 한 번 더 pull --rebase --autostash 하고 push 하면 된다. 봇 커밋과 내 커밋은 서로 다른 파일을 건드리니 충돌은 거의 안 난다 — 순서만 맞춰주면 된다.
4. 작업을 짧게 끊어 커밋한다
한 번에 6가지를 다 고치고 마지막에 몰아서 밀면, 그 긴 시간 내내 봇 커밋과 겹칠 창이 열려 있다. 그래서 한 단위씩 고치고 → 검증하고 → 바로 커밋·push를 반복했다. 실제로 이식은 이런 단위로 나눴다.
1
2
3
4
5
port: --preview 검수 하네스 (검증 인프라 먼저)
port: 006 캡션 버그
port: 007+008 폰트·콜라주
port: 009 대표 이미지
port: 010 커버 생성기
각 커밋 사이사이에 봇의 log: 커밋이 끼어들었지만, 내 커밋 단위가 작으니 겹치는 창이 좁아 pull --rebase가 매번 깔끔하게 통과했다.
🧭 기획자·사업자라면 무인 자동화 시스템은 “언제든 열어서 고칠 수 있는” 코드가 아니라 “돌아가는 중에 고쳐야 하는” 코드다. 이건 배포(deploy)에 가깝다. 그래서 여기엔 일반 개발과 다른 비용이 붙는다 — ① 변경을 작게 쪼개 배포 창을 좁히는 노력, ② 각 변경을 넣고 결과를 검증하는 단계, ③ 잘못 나가도 되돌릴 수 있는 안전장치. 자동화를 “만들면 끝”으로 잡으면 이 유지보수 비용이 안 보이지만, 실제로는 살아있는 시스템에 손대는 것 자체가 리스크다. 로드맵에 “자동화 유지보수 창”을 미리 넣어두면 급하게 라이브를 건드리다 사고 내는 걸 줄인다.
별도 함정 — 경로에 #가 있으면 스크립트가 조용히 잘린다
이 저장소는 경로가 ~/#ridev/...였다. 디렉터리 이름에 #를 쓴 건데, 이게 두 군데서 사고를 냈다.
셸에서 — # 뒤가 통째로 주석 처리된다
zsh/bash에서 #는 주석의 시작이다. 경로를 따옴표 없이 쓰면 #부터 줄 끝까지가 주석으로 날아가 경로가 잘린다.
1
2
3
4
5
# ✗ ~/ 까지만 인식하고 #ridev 이하가 주석으로 사라짐
cd ~/#ridev/project
# ✓ 항상 따옴표로 감싼다
cd "~/#ridev/project"
이건 에러가 안 나고 엉뚱한 디렉터리(홈)에서 명령이 실행되는 식으로 조용히 어긋나서 더 위험하다. 그래서 이 저장소 관련 셸 명령은 전부 경로를 "..."로 감쌌다.
file:// URL에서 — #가 프래그먼트로 해석된다
HTML을 Chrome 헤드리스로 렌더해 PNG를 뽑는 스크립트가 있었는데, 로컬 HTML을 file:// URL로 열 때 #가 URL 프래그먼트(앵커)로 해석돼 파일을 못 찾았다.
1
2
3
4
5
6
7
# ✗ file:///Users/.../#ridev/... 에서 #ridev 이하가 fragment로 떨어져 나감
subprocess.run([chrome, "--headless", f"--screenshot={out}",
"file://" + str(path)])
# ✓ 경로를 URL 인코딩한다 ('#' → '%23')
import urllib.parse
file_url = "file://" + urllib.parse.quote(str(path))
urllib.parse.quote가 #를 %23으로 바꿔줘서 프래그먼트로 오해되지 않는다. pathlib.Path.as_uri()를 써도 같은 인코딩을 해주니 둘 중 편한 걸 쓰면 된다.
사용한 기술
git pull --rebase --autostash— 봇이 만든 더티 트리를 자동 스태시하고, 내 커밋을 원격 끝(봇 커밋 위)으로 rebase한다. 병합 커밋을 안 남긴다.- 선택적 스테이징(
git add <파일>) — 봇 산출물·비밀 파일을 커밋에서 배제하는 1차 방어선. - 커밋 접두어 규약(
port:vslog:) — 사람이 넣은 변경과 봇 자동 커밋을 히스토리에서 구분한다. urllib.parse.quote/Path.as_uri()— 로컬 파일 경로를file://URL로 만들 때 특수문자를 안전하게 인코딩한다.
정리
살아있는(무인 실행 중인) 파이프라인 저장소는 일반 저장소와 다르게 다뤄야 한다.
git add -A를 버리고 코드 파일만 지정 add한다. 봇 산출물과 비밀 파일이 커밋에 섞이지 않게.pull --rebase --autostash로 봇 커밋 위에 얹고, 작업을 작게 쪼개 배포 창을 좁힌다. push가 거부되면 다시 rebase.- 경로에 특수문자(
#등)가 있으면 셸은"..."로, 파일 URL은quote()로 감싼다. 이런 건 에러 없이 조용히 어긋나서 제일 늦게 발견된다.
1편의 교훈(“명령이 나갔다 ≠ 동작했다”)과 통하는 게 있다. 자동화든 그 자동화를 고치는 작업이든, 조용히 어긋나는 실패가 제일 무섭다. 시끄럽게 실패하도록 만들고, 작게 끊어 자주 검증하는 것 — 그게 무인 시스템을 오래 살리는 규율이다.



