Tips & Tricks (업데이트: 2026. 6. 2.)

Claude Code로 Husky + lint-staged 설정하기: pre-commit 품질 가이드

Claude Code, Husky, lint-staged로 커밋 전에 ESLint와 Prettier를 자동 실행하는 실전 설정법.

Claude Code로 Husky + lint-staged 설정하기: pre-commit 품질 가이드

Claude Code를 쓰면 한 번의 작업에서 컴포넌트, 테스트, 설정, 문서가 동시에 바뀌는 일이 많습니다. 생산성은 좋아지지만 리뷰어가 모든 줄을 눈으로 확인하기는 어렵습니다. 사람이 집중해야 할 것은 동작과 설계인데, 실제 리뷰에서는 정렬이 깨진 코드, 사용하지 않는 import, Markdown 줄바꿈 같은 작은 잡음이 시간을 빼앗습니다.

Husky + lint-staged는 이 문제를 현실적으로 줄여 줍니다. Git hook은 Git이 커밋이나 푸시 같은 특정 순간에 실행하는 작은 스크립트입니다. Husky는 그 hook을 저장소 안에서 관리하게 해 주고, lint-staged는 git add된 파일에만 명령을 실행합니다. Claude Code hooks는 Claude Code 내부 생명주기에 붙는 자동화 기능입니다. 이름은 비슷하지만 Git hook을 대체하지 않습니다. Git hook은 사람이 직접 커밋하든, 에디터가 만들든, Claude Code가 만들든 항상 같은 기준을 적용합니다.

핵심은 pre-commit을 로컬 CI처럼 무겁게 만들지 않는 것입니다. 빠르고 확실한 검사는 pre-commit에 두고, 타입 체크, 테스트, 빌드는 pre-push나 CI로 넘기는 편이 오래 갑니다.

전체 흐름

실무에서 가장 안정적인 구조는 세 단계입니다. pre-commit은 파일 단위 검사, pre-push는 프로젝트 단위 검사, CI는 최종 판정입니다. 이렇게 나누면 품질은 올라가고, 개발자가 --no-verify를 습관적으로 쓰게 되는 일을 줄일 수 있습니다.

flowchart LR
  A["Claude Code가 파일 수정"] --> B["개발자가 git add로 범위 선택"]
  B --> C["Husky pre-commit"]
  C --> D["lint-staged"]
  D --> E["ESLint / Prettier"]
  E --> F["commit"]
  C --> G["타입 체크, 테스트, 빌드는 pre-push 또는 CI"]

이 글은 Husky Get started, lint-staged README, Git hooks 문서, Claude Code hooks reference를 기준으로 작성했습니다. 현재 Husky의 기본 초기화는 npx husky init이고, lint-staged는 pre-commit hook과 파일 패턴별 명령 설정을 함께 사용합니다.

최소 동작 설정

ESLint와 Prettier 설정이 아직 흔들린다면 먼저 Claude Code ESLint 설정 가이드Prettier 설정 가이드를 정리하는 것이 좋습니다. Husky는 실행 입구일 뿐이고, 실제 품질은 그 아래 명령이 결정합니다.

Claude Code에 맡길 때는 아래처럼 범위와 무게를 명확히 적습니다.

이 Node.js/TypeScript 저장소에 Husky와 lint-staged를 추가해 주세요.
pre-commit은 staged 상태의 JS, TS, JSON, Markdown, CSS 파일만 처리해야 합니다.
pre-commit에서는 ESLint 자동 수정과 Prettier만 실행하세요.
타입 체크, 테스트, 빌드는 pre-commit에 넣지 말고 pre-push 또는 CI로 분리하세요.
변경 후 사람이 확인할 명령도 짧게 정리해 주세요.

직접 설정한다면 다음 두 명령으로 시작합니다.

npm install --save-dev husky lint-staged eslint prettier
npx husky init

npx husky init.husky/pre-commit을 만들고 package.jsonprepare 스크립트를 갱신합니다. 생성된 pre-commit 내용은 이렇게 바꿉니다.

#!/usr/bin/env sh
npx lint-staged

그리고 package.json에 다음 설정을 합칩니다. 이미 scripts가 있다면 전체를 덮어쓰지 말고 필요한 키만 추가해야 합니다.

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint --fix .",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "prepare": "husky"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "*.{json,md,mdx,yml,yaml,css,scss}": [
      "prettier --write"
    ]
  }
}

확인은 일부러 정렬이 깨진 파일을 staged 상태로 만든 뒤 실행합니다.

git add src/example.ts
npx lint-staged --debug
git commit -m "chore: verify pre-commit checks"

--debug 출력에는 어떤 설정 파일을 읽었는지, 어떤 파일이 glob에 매칭됐는지, 어떤 명령이 실행됐는지가 나옵니다. hook이 실패했을 때 이 출력을 Claude Code에 주면 추측보다 훨씬 빠르게 원인을 좁힐 수 있습니다.

유지보수하기 쉬운 구성

저장소가 커지면 package.json 안에 긴 설정을 두기보다 lint-staged.config.mjs로 분리하는 편이 낫습니다. 아래 예시는 파일명에 공백이 있어도 깨지지 않도록 shell 인용을 적용합니다.

// lint-staged.config.mjs
const shellQuote = (file) => `"${file.replaceAll('"', '\\"')}"`;
const joinFiles = (files) => files.map(shellQuote).join(" ");

export default {
  "*.{js,jsx,ts,tsx}": (files) => [
    `eslint --fix --max-warnings=0 ${joinFiles(files)}`,
    `prettier --write ${joinFiles(files)}`,
  ],
  "*.{json,md,mdx,yml,yaml,css,scss}": (files) =>
    `prettier --write ${joinFiles(files)}`,
};

이 파일을 만들었다면 package.jsonlint-staged 필드는 지웁니다. 설정이 두 곳에 있으면 사람도 Claude Code도 어느 쪽이 기준인지 헷갈립니다.

세 가지 사용 사례

첫 번째는 TypeScript 제품 애플리케이션입니다. Claude Code가 컴포넌트, 테스트, 유틸리티 타입을 함께 수정할 때 ESLint 자동 수정과 Prettier만으로도 리뷰 잡음이 크게 줄어듭니다. 전체 타입 체크는 프로젝트 문맥이 필요하고 시간이 걸리므로 pre-push 또는 CI가 더 알맞습니다.

두 번째는 Astro나 Next.js 기반 콘텐츠 사이트입니다. MDX, JSON 메타데이터, CSS, TypeScript가 함께 바뀌는 경우가 많습니다. lint-staged는 커밋될 파일만 정리하기 때문에 리뷰어가 글의 내용과 구조에 집중할 수 있습니다.

세 번째는 모노레포입니다. pre-commit에서 모든 패키지 테스트를 실행하면 거의 반드시 느려집니다. staged 파일 포맷팅과 lint만 먼저 막고, 패키지별 테스트는 CI나 태스크 러너가 경로 기준으로 고르게 하는 편이 안정적입니다. Claude Code에는 “pre-commit은 빠르게, pre-push는 중간 검사, CI는 전체 검사”라고 규칙을 적어 두면 과한 hook을 만들 가능성이 줄어듭니다.

commit-msg와 pre-push 분리

커밋 메시지 규칙은 commit-msg hook에서 다루는 것이 깔끔합니다. Conventional Commits를 사용한다면 commitlint를 추가합니다.

npm install --save-dev @commitlint/cli @commitlint/config-conventional
// commitlint.config.mjs
export default {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "subject-max-length": [2, "always", 72],
  },
};
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"

마지막 shell 코드는 .husky/commit-msg에 둡니다. 빌드, 테스트, 타입 체크는 .husky/pre-push에서 실행합니다.

#!/usr/bin/env sh
npm run validate

package.json의 검증 스크립트는 CI와 같은 이름으로 맞추면 좋습니다.

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "test:ci": "vitest run --coverage",
    "build": "vite build",
    "validate": "npm run typecheck && npm run lint && npm run format:check && npm run test:ci && npm run build"
  }
}

실제로 자주 생기는 함정

가장 흔한 실패는 pre-commit에 너무 많은 것을 넣는 것입니다. 커밋마다 2분이 걸리면 사람들은 품질 기준을 존중하기보다 우회 방법을 익힙니다. 몇 초 안에 끝나는 hook만 습관이 됩니다.

두 번째는 부분 staged 상태입니다. lint-staged는 staged 파일을 대상으로 하지만, 같은 파일 안에 unstaged 변경이 남아 있을 수 있습니다. 이상한 결과가 나오면 git status --short, git diff --staged, npx lint-staged --debug를 함께 확인합니다.

Windows와 macOS, Linux가 섞인 팀에서는 줄바꿈도 중요합니다. Husky hook은 shell 스크립트이므로 LF로 저장하는 편이 안전합니다.

* text=auto eol=lf
*.cmd text eol=crlf
*.bat text eol=crlf

|| true로 실패를 숨기는 수정도 피해야 합니다. 그것은 품질 게이트를 통과시키는 것이 아니라 없애는 것입니다. 오류 메시지를 줄이고, 무거운 명령을 pre-push로 옮기고, 수정 명령을 문서화하는 쪽이 맞습니다.

직접 확인한 결과

작은 TypeScript/Vite 프로젝트에 이 구성을 넣고 포맷 오류, 사용하지 않는 import, MDX 공백 문제를 일부러 만들었습니다. pre-commit은 staged 파일만 처리해서 몇 초 안에 끝났고, npx lint-staged --debug로 대상 파일을 바로 확인할 수 있었습니다. 타입 오류는 pre-commit이 아니라 pre-push의 npm run validate에서 막는 편이 커밋 흐름을 덜 방해했습니다.

정리

Husky + lint-staged는 Claude Code로 커진 변경량을 사람이 전부 눈으로 감당하지 않게 해 주는 현실적인 안전장치입니다. pre-commit은 가볍게, pre-push와 CI는 더 깊게, Claude Code에는 그 분담을 명시하세요. 그러면 hook은 번거로운 의식이 아니라 팀 전체의 품질 기준이 됩니다.

다음 단계로 ESLint 설정을 다듬고 Prettier 설정으로 포맷 규칙을 통일해 보세요.

#Claude Code #Husky #lint-staged #Git hooks #코드 품질
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.