Skip to content
게으른 엔지니어의 기술 블로그
Go back

Hermes에게 lint 정리를 맡겼다가 — 보고도, 내 계산도 둘 다 다시 확인해야 했다

ESLint 크래시를 고치고 나니, 그 크래시 뒤에 가려져 있던 기존 lint 문제 794건이 드러났다. 그중 상당수는 실제 타입 안전성 위반이었는데, 규칙이 뚜렷하고 기계적으로 반복되는 작업이라 AI 에이전트에게 맡겨보기로 했다. 마침 Hermes를 GLM-5.2로 돌리고 있어서, 격리된 git worktree에서 작업하게 하고 결과만 리뷰하는 방식으로 진행했다.

첫 번째 시도 — “완료”라는데 사실 아무것도 안 했다

작업 범위와 검증 방법까지 꽤 자세히 적어서 넘겼다. 얼마 뒤 돌아온 보고는 그럴듯했다.

Problem: npm run lint crashed (exit code 2)...
Fix (1 file, eslint.config.mts, +14/-1): ...
Verification: npm run lint: before = exit 2 (crash), after = exit 1 ...

정상적으로 뭔가를 해낸 것처럼 보였다. 그런데 자세히 읽어보니 이상했다 — 내가 시킨 건 794건의 lint 백로그였는데, 보고 내용은 온통 “크래시를 고쳤다”는 얘기뿐이었다. 그 크래시는 이미 며칠 전에 내가 직접 고쳐서 커밋까지 해둔 거였다.

브랜치를 열어봤다.

git log --oneline hermes/hermes-715c7eb8 -5
# cbb8f44 fix(lint): disable type-checked obsidianmd rules for non-TS test files
# 23abf1f Clarify BRAT install steps apply to desktop too, not just mobile

부모 커밋이 23abf1f였다. 내가 크래시를 고친 커밋(7f836ca)보다 앞선 시점이다. worktree가 내 로컬 브랜치가 아니라 origin/master를 기준으로 만들어졌던 거고, 나는 그 크래시 수정 커밋을 push한 적이 없었다. 그러니 Hermes 입장에서는 크래시가 아직 안 고쳐진 상태로 보였을 거고, 처음 보는 문제인 줄 알고 처음부터 다시 고친 다음 거기서 작업을 끝냈다. 내가 이미 끝낸 일을 중복으로 시킨 셈이었다.

다행히 완전히 헛수고는 아니었다. Hermes가 고친 버전이 내 것보다 하나 더 나았다 — tests/mocks/obsidian.js라는 파일이 내 수정 범위(tests/**/*.mjs)에서 빠져 있었는데, 그것까지 잡아낸 거였다. 이 부분만 골라서 내 수정에 합쳤다.

두 번째 시도 — 이번엔 진짜로

원인을 알았으니 고치는 건 간단했다. 밀린 커밋을 push하고, worktree를 새로 만들어서 진짜 작업(타입 안전성 위반 정리)을 다시 시켰다. 이번엔 브랜치가 최신 master를 기준으로 제대로 만들어진 걸 확인하고 시작했다.

얼마 뒤 다시 “완료” 보고가 왔다. 이번에도 곧이곧대로 믿지 않고 직접 확인했다.

npx eslint . -f json 2>/dev/null > /tmp/lint2.json
# ... 규칙별로 집계 ...

no-unsafe-* 계열과 no-explicit-any — 지난번엔 있었던 카테고리들이 이번엔 전부 0건이었다. 전체 spec 30개도 전부 통과, 빌드도 정상. 이번엔 보고와 실측이 정확히 일치했다.

근데 숫자가 안 맞았다

여기까지 정리하면서 “542건을 해결했다”고 적었는데, 다시 보니 이상했다. 이 문제를 처음 발견했을 때 크래시 수정 글에는 “471개는 실제 타입 안전성 관련 위반”이라고 적어뒀었다. 471과 542, 둘 다 내가 쓴 숫자인데 서로 안 맞았다.

원인은 단순했다. 471은 크래시를 막 잡았을 때 급하게 어림잡은 숫자였다.

no-unsafe-member-access 244
+ no-unsafe-assignment   114
+ no-unsafe-call          62
+ no-unsafe-argument      51
= 471

딱 봐도 관련 있어 보이는 규칙 4개만 더해서 471을 냈던 거다. 그런데 실제로 관련된 규칙은 6개였다 — no-unsafe-return(5건)과 no-explicit-any(66건)를 빼먹었다.

471 + no-unsafe-return(5) + no-explicit-any(66) = 542

나중에 스크립트로 정확히 집계했을 때 나온 542가 맞는 숫자였고, 471은 처음에 대충 더한 게 그대로 블로그 글과 문서에 박제된 거였다. AI가 보낸 보고만 의심할 게 아니라, 내가 직접 암산으로 낸 숫자도 다시 확인이 필요했던 거다.

그리고 542를 고쳤는데 왜 262가 남았을까

숫자를 바로잡고 나니 또 다른 게 안 맞았다. 원래 794건이었고 542건을 고쳤으면 794 - 542 = 252건이 남아야 하는데, 실제로 다시 lint를 돌려보니 262건이 남아 있었다. 10건 차이가 났다.

이번엔 진짜 이상 현상이라 다시 규칙별로 비교해봤다. 그랬더니 원래 목록엔 아예 없던 규칙들이 새로 등장해 있었다 — no-base-to-string(4건), restrict-template-expressions(2건), rule-custom-message(1건). 그리고 있던 규칙 중 일부는 오히려 건수가 늘어 있었다 — no-unnecessary-type-assertion이 6건에서 8건으로, no-unused-vars의 타입스크립트 버전이 17건에서 19건으로.

이유를 알고 보니 당연한 결과였다. any 타입은 그 값에 대한 타입 체크 자체를 건너뛴다. 그러니 any로 남아있던 값들은 no-base-to-string이나 restrict-template-expressions 같은, 애초에 타입이 확정돼야 검사할 수 있는 규칙들의 사정권 밖에 있었던 거다. Hermes가 any를 실제 타입으로 바꾸자 그 값들이 처음으로 타입 체크 대상이 됐고, 그 순간 전에는 안 보이던 위반이 새로 드러난 거다. 문제를 제대로 고쳤다는 증거이기도 하지만, 동시에 “542건 해결”이라는 말만으로는 실제 순감소(532건)를 정확히 설명할 수 없다는 뜻이기도 했다.

남는 교훈

이번엔 교훈이 두 겹이었다.

하나는 익숙한 거다 — AI 에이전트가 보낸 “완료” 보고를 그대로 믿지 않고 매번 직접 lint/build/전체 테스트를 다시 돌려서 확인했다. 실제로 첫 번째 시도에서 이 습관이 없었다면, 아무 일도 안 한 걸 “완료됐다”고 그냥 넘어갈 뻔했다.

다른 하나는 이번에 새로 배운 거다 — 검증이 필요한 건 에이전트의 보고만이 아니라, 내가 직접 계산한 숫자도 마찬가지였다. 471이라는 숫자는 AI가 준 게 아니라 내가 급하게 암산으로 낸 거였고, 그게 그대로 공개된 글에 들어갔다. 그리고 “542건 고쳤다”는 문장도, 실제로 스크립트를 돌려서 재검증하기 전까진 정확히 뭘 의미하는지 나 스스로도 몰랐다.

결국 이 글에 나온 숫자들 — worktree가 어느 커밋에서 갈라졌는지, 규칙별 개수가 몇 건인지, 전후 차이가 몇 건인지 — 전부 기억이나 암산이 아니라 실제로 명령어를 돌려서 나온 값이다. 일을 시킨 대상이 AI든 나 자신이든, 숫자는 재현 가능한 방법으로 다시 뽑아봐야 믿을 수 있다는 게 이번에 다시 확인한 원칙이다.


Share this post:

Next Part
Hermes에게 lint 정리를 맡겼다가 2편 — YAML 날짜가 조용히 사라진 이유