← 노하우 목록

FROM THE WORKSHOP / 노하우

detached HEAD에서 자동으로 커밋하면 push가 조용히 실패한다 — 커밋 전 브랜치 앵커를 단언한다

자동 커밋이 detached HEAD 위에서 성공하면 커밋은 생기지만 브랜치에 붙지 않아 push가 조용히 실패한다. 커밋 직전에 symbolic-ref로 목표 브랜치 앵커를 단언하고, 같은 저장소 쓰기는 flock으로 직렬화하며, detached 상태를 일부러 만든 회귀 테스트로 예방 게이트를 굳힌다.

강대종 · 마이너스베타스튜디오

detached HEAD에서 자동으로 커밋하면 push가 조용히 실패한다 — 커밋 전 브랜치 앵커를 단언한다

자동화가 주기적으로 git 저장소에 뭔가를 기록하고 push 하도록 만들어 두면, 어느 날 그 기록이 44시간 동안 아무 데도 안 올라가고 붕 떠 있는 걸 뒤늦게 발견하게 된다. 화면에는 에러도 안 뜬다. 커밋은 성공했고, 다만 그 커밋이 어떤 브랜치에도 붙어 있지 않은 detached HEAD 위에 얹혔을 뿐이다.

무슨 일이 벌어지나

기록용 자동화는 보통 쓰기 전에 원격과 맞추려고 git rebase origin/main 같은 걸 먼저 돈다. 그런데 그 rebase가 완료 직전에 중단되면(프로세스 종료, 절전, 타임아웃) HEAD가 main으로 되돌아오지 못하고 특정 커밋을 직접 가리키는 detached 상태로 남는다.

문제는 그 다음이다. 코드가 “지금 HEAD가 브랜치 위에 있나?”를 확인하지 않고 그냥 git commit을 하면, 커밋 자체는 멀쩡히 성공한다. 하지만 그 커밋은 브랜치에 매달리지 않은 고아 커밋이라 git push로 올라가지 않는다. 자동화는 “커밋 성공”만 보고 다음 주기로 넘어가고, 데이터는 계속 붕 뜬 HEAD에만 쌓인다. 다음 세션이 정상 체크아웃하면 그 커밋들은 아예 안 보인다.

왜 이게 위험한가

  • 에러가 안 난다. commit은 성공하니까 실패로 안 잡힌다. 조용한 실패가 제일 늦게 발견된다.
  • 누적된다. 한 번 detach되면 이후 모든 기록이 같은 고아 HEAD에 쌓여 며칠치가 한꺼번에 갇힌다.
  • 경합이 2차로 겹친다. 여러 프로세스가 락 없이 같은 저장소에 쓰면 detach 확률이 더 오른다.

룰 — 자동 커밋 전에 “나는 지금 main 위에 있다”를 단언한다

사람이 아니라 코드가 매번 확인하게 만든다.

# 커밋 직전 게이트
if [ "$(git symbolic-ref -q HEAD)" != "refs/heads/main" ]; then
    # detach 감지 → 커밋 차단
    #  - 작업 트리가 clean 하면 git checkout main 으로 자동 복구
    #  - 아니면 커밋 멈추고 사람에게 알림
    exit 1
fi
git commit ...

여기에 두 겹을 더한다.

  • flock: 같은 저장소에 쓰는 경로 전체를 하나의 락으로 감싸 동시 실행 경합을 막는다.
  • detach 시뮬 테스트: HEAD를 일부러 detach시킨 상태에서 자동화를 돌려, 게이트가 실제로 커밋을 막는지 확인하는 회귀 테스트를 붙인다. 재발 방지는 문장이 아니라 테스트로 굳힌다.

감지와 예방은 다른 일이다

“저장소가 비정상 상태(비-main, dirty, 밀림)면 알림” 같은 감지 가드는 이 사고를 표면화하는 데 큰 역할을 한다. 실제로 44시간 멈춘 걸 붙잡아 알린 게 이 감지 가드였다. 하지만 감지는 이미 벌어진 고아 커밋을 알려줄 뿐, 생성 자체를 막지는 못한다. 감지 가드가 있어도 예방 게이트는 따로 필요하다.

복구는 preserve-first로

이미 갇혔다면 순서가 중요하다. 절대 force push나 리셋으로 밀지 않는다.

  1. detached HEAD를 salvage 태그로 먼저 박제한다. 유실 방지가 먼저다.
  2. main을 체크아웃하고 origin으로 fast-forward한다.
  3. 갇혀 있던 커밋을 cherry-pick으로 옮긴다.
  4. 강제 아닌 일반 push로 올린다. 올라간 걸 실측으로 확인한다.

이렇게 하면 데이터 유실 0으로 복구된다.

체크리스트

자동으로 git에 쓰는 코드를 만들거나 볼 때.

  1. 커밋 직전에 HEAD가 목표 브랜치 위인지 단언하는가?
  2. detach를 감지하면 커밋을 멈추거나 자동 복구하는가?
  3. 동시 실행을 flock으로 직렬화했는가?
  4. detach 상태를 시뮬레이션하는 회귀 테스트가 있는가?
  5. 감지 알림과 예방 게이트를 둘 다 갖췄는가, 아니면 감지만 있는가?

한 문장으로. 커밋이 성공했다는 건 데이터가 안전하다는 뜻이 아니다. 그 커밋이 브랜치에 붙어 있고 원격에 올라갔을 때 비로소 안전하다.

기록은 다음 시작이 됩니다.노하우 더 읽기 ↗이 작업장의 제품 ↗
작업장 전체 페이지 목적에 맞는 페이지 찾기