Skip to content

Claude Code 커밋 생성 자동화 — 커밋은 명령이 아니라 절차다(`status`→`diff`→`log`→메시지→`add`→`commit`), '무엇'이 아니라 '왜'를 쓰는 스마트 메시지, Conventional Commits 형식, HEREDOC 멀티라인, 그리고 `Co-Authored-By` 트레일러와 `attribution` 설정

Published: at 09:26 PM

들어가며

37차시는 **섹션 8(Git 통합 워크플로우)**의 토대를 깔았다. 변경을 읽는 세 명령(status·diff·log), 변경을 분석하는 단계, 그리고 그 위를 받치는 안전 원칙까지. 한 문장으로 줄이면 37차시는 읽기와 안전이었다. 아직 저장소에 아무것도 쓰지 않았다.

38차시부터 달라진다. 이번이 그 토대 위에서 처음으로 쓰는 작업이다 — 커밋 생성. 분석한 변경을 영구 기록(37차시의 permanent history)에 묶어 남기는 첫 단계다.

커밋을 “마지막에 한 번 치는 명령” 정도로 보기 쉽다. 그런데 에이전트 시대에는 오히려 반대다. 커밋이 더 중요해진다. 이유는 단순하다.

그래서 이 차시는 “그냥 커밋하는 법”이 아니라 좋은 커밋을 자동으로 만드는 법을 다룬다 — 스마트 메시지 생성의 절차, Conventional Commits 형식, 본문을 위한 HEREDOC, 그리고 기여를 정직하게 표시하는 Co-Authored-By 트레일러까지.

먼저 흔한 오해 하나를 걷어내고 시작한다.

/commit 같은 건 없다 — 커밋은 자연어로 부른다

커밋을 /commit 같은 전용 슬래시 명령으로 떠올리기 쉽다. 하지만 현재 Claude Code의 내장 명령 목록에 /commit은 없다. Git과 엮인 내장은 변경을 보는 /diff(커밋 안 된 변경 뷰어), /code-review, /security-review, GitHub 연동용 /install-github-app 정도다.

커밋은 명령이 아니라 요청으로 시작한다. 자연어로 부탁하면 된다.

변경사항 커밋해줘

그러면 Claude가 뒤에서 Bash 도구로 커밋 절차를 돈다. 베스트 프랙티스 문서의 4단계 워크플로우(Explore → Plan → Implement → Commit)에서도 마지막 Commit 스텝의 프롬프트는 이렇게 자연어다.

설명적인 메시지로 커밋하고 PR을 열어줘

이건 37차시의 원칙과도 정확히 맞물린다 — 커밋·푸시는 요청해야만 한다. Claude는 변경을 만들었다고 알아서 커밋으로 넘어가지 않는다. “커밋해줘”라고 명시적으로 말해야 이 절차로 들어간다.

커밋은 “명령”이 아니라 “절차”다

사람도 좋은 커밋을 만들 때 그냥 git commit만 치지 않는다. 먼저 git status·git diff로 뭐가 바뀌었나 보고, 기존 로그를 흘끗 보고 스타일을 맞춘 다음, 메시지를 쓴다. Claude Code의 커밋 자동화도 똑같은 절차를 명시적으로 밟는다.

단계명령무엇을 하나
1git status무엇이 바뀌었나 — 수정·추가·삭제, 스테이징 여부
2git diff (+ --staged)어떻게 바뀌었나 — 줄 단위 변경
3git log이 저장소의 기존 커밋 스타일
4(메시지 작성)변경의 의도를 한 줄로, 필요하면 본문까지
5git add관련된 파일만 스테이징
6git commitHEREDOC으로 실행
7git status커밋이 제대로 됐는지 확인

1~3번은 전부 읽기 전용 git이다. 37차시에서 봤듯 권한 프롬프트 없이 자동으로 돌고, 그래서 Claude는 종종 셋을 한 번에 병렬로 읽는다 — 공짜이고 안전하니까.

여기서 강조할 게 하나 있다. “스마트 커밋 메시지 생성”의 절반은 3번, git log다. 저장소마다 커밋 컨벤션이 다르다 — 어떤 팀은 feat:을 쓰고, 어떤 팀은 한 줄로만 끝내며, 어떤 팀은 본문에 변경 목록을 붙인다. Claude는 커밋 메시지를 쓰기 전에 git log로 기존 스타일을 읽고 거기에 맞춘다. 37차시에서 git log를 “이 저장소는 어떻게 일해 왔나”를 묻는 명령이라 부른 이유가 바로 여기서 쓰인다.

좋은 커밋 메시지 — “무엇”이 아니라 “왜”

커밋 메시지를 쓸 때 가장 흔한 실수는 무엇이 바뀌었는지를 그대로 옮겨 적는 것이다. 그런데 잘 생각해 보면, git diff가 이미 “무엇”을 정확히 보여준다. 어떤 줄이 더해지고 빠졌는지는 diff에 다 있다. 메시지가 그걸 또 반복하면 정보가 0이다.

좋은 메시지는 diff가 말해주지 않는 것를 담는다. 이 변경이 어떤 문제를 풀려는 건지, 어떤 의도인지.

나쁜 예무엇이 문제인가
update code무엇도 왜도 없다
change login.jsdiff를 그대로 옮겼다(“무엇”의 반복)
fix bug어떤 버그인지, 왜 그게 버그였는지 없다
좋은 예무엇이 좋은가
feat: 로그인 폼에 비밀번호 표시 토글 추가무엇을 했고(토글 추가) 어디에(로그인 폼) 명확
fix: 세션 만료 후 토큰 갱신 실패 수정증상과 맥락(“세션 만료 후”)이 드러난다

이게 바로 “스마트 메시지 생성”의 정체다. Claude는 diff 안에 흩어진 줄 변경을 읽어 하나의 의도로 묶는다 — 37차시에서 본 변경 분석(“이건 로그인 폼에 비밀번호 토글을 더한 변경”)의 직접적인 연장이다. 그 한 줄 요약이 커밋의 제목이 된다.

요약 줄의 관례도 간단하다. 짧고, 명령형(imperative)으로. “추가했음”이 아니라 “추가”. 그리고 자명하지 않은 변경이라면, 빈 줄 하나 뒤에 를 설명하는 본문을 붙인다(뒤의 HEREDOC 절에서 다룬다).

Conventional Commits 형식

요약 줄을 일관된 구조로 쓰는 가장 널리 쓰이는 규약이 Conventional Commits다. 형식은 이렇다.

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

type은 이 커밋이 무슨 종류인지를 한 단어로 못 박는다. 대표적인 둘과 나머지는 이렇다.

타입의미SemVer
feat새 기능 추가MINOR
fix버그 수정PATCH
docs문서만 변경
refactor동작 변화 없는 구조 개선
test테스트 추가·수정
perf성능 개선
build·ci·chore·style빌드·CI·잡무·포맷

스펙이 못 박는 핵심 규칙 몇 가지를 그대로 옮기면 이렇다.

커밋은 반드시 타입(feat, fix 등)으로 시작해야 한다. 설명(description)은 타입/스코프 뒤의 콜론과 공백 바로 다음에 와야 한다. 본문(body)은 설명에서 빈 줄 하나를 둔 뒤 시작해야 한다.

**파괴적 변경(BREAKING CHANGE)**은 두 방식으로 표시한다 — 타입 뒤에 !를 붙이거나(feat!:), 푸터에 BREAKING CHANGE: <설명>을 적는다. 둘 다 SemVer의 MAJOR에 해당한다.

feat!: 인증 토큰 형식을 JWT로 교체

BREAKING CHANGE: 기존 세션 토큰은 더 이상 유효하지 않다. 재로그인이 필요하다.

그런데 왜 에이전트에게 이런 컨벤션이 특히 잘 맞을까? 컨벤션은 사람과 기계가 함께 읽는 계약이기 때문이다. feat·fix 같은 접두사가 일관되면, changelog 생성기나 semantic-release 같은 도구가 커밋만 보고 버전을 올리고 릴리스 노트를 만든다. Claude는 이런 규칙을 일관되게 지키는 데 강하다.

다만 중요한 단서가 있다. Claude는 당신 저장소의 컨벤션을 따른다. 절차 3번(git log)에서 기존 스타일을 읽기 때문이다. 저장소가 Conventional Commits를 쓰면 맞추고, 안 쓰면 굳이 들이밀지 않는다. 그러니 반드시 이 형식을 쓰게 하려면, 매번 말하는 대신 CLAUDE.md에 한 줄 박아 두면 된다(21차시).

# 커밋 규칙
- 커밋 메시지는 Conventional Commits 형식을 따른다

베스트 프랙티스 문서도 CLAUDE.md에 넣을 것으로 “Repository etiquette (branch naming, PR conventions)” 를 꼽는다 — 커밋·PR 컨벤션이 정확히 그 자리다.

본문과 HEREDOC

요약 한 줄로 충분한 커밋도 많지만, 자명하지 않은 변경에는 본문을 붙인다 — 그렇게 했는지, 그리고 핵심 변경 목록. 문제는, 여러 줄짜리 메시지를 명령행에서 만들 때다. 따옴표·개행·셸 변수($)·백틱이 뒤엉키기 쉽다.

그래서 Claude는 멀티라인 커밋을 HEREDOC으로 만든다.

git commit -m "$(cat <<'EOF'
feat: 로그인 폼에 비밀번호 표시 토글 추가

- 비밀번호 필드에 eye 아이콘 추가
- 클릭 시 표시/숨김 토글
- 접근성 aria-label 추가

Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"

여기서 포인트는 **따옴표로 감싼 'EOF'**다. 구분자를 작은따옴표로 감싸면 셸이 본문 안의 $변수나 백틱을 해석하지 않고 글자 그대로 둔다. 그래서 메시지에 $NODE_ENV 같은 문자열이나 백틱 든 코드 조각이 들어가도 안전하게 보존된다. 멀티라인·특수문자·정확한 포맷을 한 번에 해결하는 관용구다.

Co-Authored-By — 기여를 정직하게 표시한다

위 예시 마지막 줄을 보자.

Co-Authored-By: Claude <noreply@anthropic.com>

이건 Git의 **트레일러(trailer)**다. 커밋 메시지 맨 끝에 Key: Value 형태로 붙는 메타데이터로, GitHub이 Co-Authored-By를 인식해 해당 커밋의 공동 작성자(co-author) 목록에 Claude를 표시한다. 활성 모델에 따라 모델명이 함께 들어가기도 한다(예: Claude Opus, Claude Sonnet, Claude Haiku).

이 트레일러는 37차시의 한 원칙과 정확히 맞닿아 있다 — Claude는 git config를 건드리지 않는다. 즉 커밋의 작성자(author) 정보는 사람 그대로 둔다. 그러면서 Claude의 기여는 작성자를 위조하지 않고 트레일러로 덧붙여 표시한다. 작성자는 당신, 공동 기여자는 Claude — 이게 정직한 표시 방식이다.

참고로 커밋에는 Co-Authored-By 트레일러가, PR에는 별도의 푸터가 붙는다.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

PR 쪽 푸터는 39차시에서 다룬다. 여기서는 커밋 = 트레일러, PR = 푸터로 자리만 잡아 두면 된다.

기여 표시를 끄거나 바꾸기 — attribution 설정

팀 정책에 따라 이 기여 표시를 원치 않을 수 있다. 끄거나 바꾸는 건 설정 한 줄이다.

예전에는 includeCoAuthoredBy로 켜고 껐다. 공식 문서가 이렇게 설명한다(현재는 deprecated).

Deprecated: attribution을 대신 쓰라. git 커밋과 PR에 co-authored-by Claude byline을 넣을지 여부(기본값: true).

지금 권장되는 방식은 attribution 설정이다(v2.0.62에서 도입). 커밋과 PR을 따로 커스터마이즈할 수 있고, 각 값을 빈 문자열("")로 두면 그 위치의 표시가 꺼진다.

기여 표시를 완전히 끄려면 settings.json에 이렇게 둔다.

{
  "attribution": {
    "commit": "",
    "pr": ""
  }
}

문구를 바꾸려면 원하는 텍스트를 넣는다 — 예컨대 커밋에는 byline을, PR에는 아무것도 안 붙이는 식이다.

{
  "attribution": {
    "commit": "🤖 Generated with Claude Code",
    "pr": ""
  }
}

기본값은 표시가 켜져 있다는 점만 기억하면 된다. 끄고 싶다면 명시적으로 비워 둔다.

37차시의 안전 원칙은 커밋 단계에 그대로 적용된다

커밋은 쓰는 작업이라, 37차시에서 본 모든 안전 원칙이 여기서 살아난다. 커밋 맥락으로 다시 짚으면 이렇다.

그리고 이 선들은 이 아니라 **권한 시스템(17차시)**으로 강제할 수 있다. 37차시에서 본 그 예시가 정확히 이 차시의 주제를 담는다 — 커밋은 자동 허용, 푸시는 차단.

{
  "permissions": {
    "allow": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}

비대화형 배치(헤드리스)에서도 같은 원리다. 베스트 프랙티스 문서의 fan-out 예시는 편집과 커밋만 열어 둔다 — --allowedTools "Edit,Bash(git commit *)". 커밋까지는 자율, 푸시는 사람의 손에 — 이 비대칭이 안전한 자동화의 기본형이다.

흔한 함정

  1. /commit 슬래시 명령을 찾는다. 그런 내장 명령은 없다. “커밋해줘”라는 자연어로 부르고, Claude가 Bash로 절차를 돈다.
  2. 메시지에 “무엇”만 쓴다. git diff가 이미 무엇을 보여준다. 메시지엔 를 쓴다 — update code 말고 fix: 세션 만료 후 토큰 갱신 실패 수정.
  3. Conventional Commits가 항상 강제된다고 믿는다. Claude는 git log저장소 기존 스타일을 읽어 맞춘다. 반드시 쓰게 하려면 CLAUDE.md에 명시한다(21차시).
  4. 멀티라인 메시지를 -m "..." 여러 개로 욱여넣는다. 본문 있는 커밋은 HEREDOC("$(cat <<'EOF' … EOF)")으로. 작은따옴표 'EOF'가 셸 해석을 막아 준다.
  5. Co-Authored-By가 작성자를 바꾼다고 오해한다. 작성자(author)는 그대로다. 트레일러는 공동 기여만 덧붙인다. Claude는 git config를 건드리지 않는다(37차시).
  6. 트레일러를 못 끄는 줄 안다. attribution 설정(구 includeCoAuthoredBy)으로 commit·pr을 각각 비우거나 바꾼다.
  7. 테스트가 막힌다고 --no-verify로 넘긴다. pre-commit 훅은 팀의 품질 게이트다. 우회가 아니라 고치는 게 맞다.
  8. 한 커밋에 여러 갈래를 다 몰아넣는다. 관련된 변경끼리 묶어 의미 있는 단위로 커밋한다 — 그래야 git log도, 나중의 리뷰·되돌리기도 깔끔하다.
  9. 커밋이 자동으로 됐다고 기대한다(혹은 그 반대). Claude는 요청해야 커밋한다. 자동화하려면 allow Bash(git commit *)를 명시하되, 푸시는 deny Bash(git push *)로 막아 두는 편이 안전하다.

정리

핵심 요점

  1. 커밋은 명령이 아니라 절차다status(무엇이)→diff(어떻게)→log(기존 스타일)→메시지→addcommitstatus(확인). 앞 셋은 읽기 전용 git이라 자동·병렬로 돈다. 스마트의 절반은 git log로 저장소 컨벤션에 맞추는 데 있다.
  2. 좋은 메시지는 “무엇”이 아니라 “왜”다. diff가 이미 무엇을 보여주므로, 메시지는 의도를 담는다. 흩어진 변경을 하나의 의도로 묶는 것(37차시 변경 분석의 연장)이 “스마트 메시지 생성”의 정체다.
  3. Conventional Commits = <type>[scope]: <description> + 본문 + 푸터. feat→MINOR, fix→PATCH, BREAKING CHANGE(! 또는 푸터)→MAJOR로 SemVer와 맞물린다. 단 Claude는 당신 저장소의 컨벤션을 따르므로, 강제하려면 CLAUDE.md에 적는다.
  4. 본문 있는 멀티라인은 HEREDOC으로. "$(cat <<'EOF' … EOF)"에서 작은따옴표 'EOF'가 셸의 $·백틱 해석을 막아 메시지를 글자 그대로 보존한다.
  5. Co-Authored-By: Claude <noreply@anthropic.com> 트레일러로 정직하게 표시한다. GitHub이 공동 작성자로 인식하고(모델명이 들어갈 수 있다), git config는 건드리지 않은 채 작성자는 사람·기여는 트레일러로 둔다. 끄거나 바꾸려면 attribution 설정(구 includeCoAuthoredBy).
  6. 37차시 안전 원칙은 커밋에 그대로 적용된다요청해야만 커밋, 기본 브랜치면 먼저 브랜치, 훅 스킵(--no-verify) 금지, --amend보다 새 커밋, 민감 파일 제외. allow Bash(git commit *) + deny Bash(git push *)로 못 박는다.

다음 단계

38차시가 하나의 커밋을 잘 만드는 법이었다면, 39차시는 그 커밋들을 모아 PR로 올리는 법이다. 핵심 차이가 하나 있다 — PR은 최신 커밋 하나가 아니라 브랜치의 모든 커밋을 분석해 제목과 본문을 쓴다. 그리고 이 차시에서 본 커밋의 Co-Authored-By 트레일러가, PR에서는 🤖 Generated with Claude Code 푸터로 이어지고, gh CLI로 실제 PR이 만들어진다. 38차시에서 “좋은 커밋”을 봤으니, 39차시에서 그 커밋들이 어떻게 좋은 PR이 되는지로 이어진다.

참고 자료


Previous Post
Claude Code PR 생성과 관리 — PR은 *최신 커밋 하나*가 아니라 *브랜치 전체*를 본다(`git diff main...HEAD`), `## Summary`+`## Test plan` 본문, `gh pr create`와 HEREDOC, `🤖 Generated with Claude Code` 푸터, 그리고 세션을 PR에 잇는 `claude --from-pr`
Next Post
Claude Code Git 기본 워크플로우 — `git status`·`diff`·`log`로 변경 읽기, 체크포인트(`/rewind`)와 Git의 차이, 그리고 내장 안전 원칙(force push·`--no-verify`·`--amend`·git config)