운영 중인 ERP에서 Claude Code와 함께한 60일: 솔직한 균형 (과장 없이, 가감 없는 수치로)
요약
운영 중인 ERP 환경에서 Claude Code를 60일간 실무에 적용하며 얻은 정량적 데이터를 공유합니다. 마케팅용 성공담이 아닌, 커밋 수와 코드 라인 수 등 실제 수치를 바탕으로 AI 어시스턴트의 실질적인 생산성과 한계를 분석합니다.
핵심 포인트
- 60일간 총 984개의 커밋 기록 (일 평균 16개)
- 약 13만 줄 이상의 TypeScript 기반 코드 생성 및 관리
- 74개의 아키텍처 결정 및 276개의 DB 마이그레이션 수행
- 단순 데모가 아닌 실제 운영 환경에서의 정량적 감사(Audit) 제공
Étienne이 수치를 보여달라고 요청한 저녁
화요일 저녁, 일과가 끝날 무렵 오픈 스페이스에는 Étienne을 제외하고는 아무도 없었습니다. Étienne은 이 회사의 지분 60%를 보유하고 있으며, 소프트웨어 퍼블리셔를 인수하는 펀드에서 근무하며 일주일을 보냅니다. 그는 다른 사람들이 재무제표를 읽는 것처럼 일 년 내내 ERP를 살펴봅니다. 그는 손에 금속 물병을 든 채 내 책상 모서리에 앉아, 누군가가 스스로에게 이야기를 들려주고 있다고 느낄 때마다 늘 하는 말을 했습니다. "그게 무엇에 근거한 거죠? (What's that based on?)"
나는 서사(narrative)로 대답하려던 참이었습니다. Claude Code와 함께 Rembrandt라는 운영 환경에서 단독으로 보낸 60일, 그 교리(doctrine)를 배우고, 진행 중 발생한 철회(retractions), 그리고 규칙을 견고하게 만든 사고(incidents)들에 대한 이야기 말입니다. 선언적인 형태의 답변이 준비되어 있었습니다. 하지만 Étienne은 서사를 요구하는 것이 아니라, 실질적인 인벤토리(material inventory)를 요구합니다. 그래서 나는 터미널을 열고 wc -l이 말하게 했습니다. 이 글은 그가 묻기 전에 내가 그에게 주었어야 했던 것입니다. 즉, 건조하고 숫자로 된 잔액, 무엇이 작동했고 무엇이 작동하지 않았는지, 그리고 내가 무엇을 다르게 할 것인지에 대한 내용입니다. 성공담도, _주의를 주는 이야기(cautionary tale)_도 아닙니다. 그저 우리 모두가 빛나는 부분만을 게시하느라 너무 바빠서 아무도 DEV.to에서 실행하지 않는 감사(audit)일 뿐입니다.
Étienne의 질문 뒤에 걸려 있는 것은 장치의 성능이라기보다, 그것을 정직하게 측정할 수 있는 가능성에 가깝습니다. 운영 프로젝트에서 AI 어시스턴트와 함께 60일 동안 연습한 사례는 현 단계에서 보기 드문 대상입니다. 이 주제로 유통되는 대부분의 게시물은 해커톤에서의 짧은 데모이거나 벤더들의 마케팅 발표입니다. 수치와 철회를 동반한 60일간의 현장 피드백은 거의 존재하지 않습니다. 그것이 바로 내가 여기서, 엄격하게 필요한 수준 이상의 교육적 설명 없이 메우고자 하는 간극입니다.
건조한 실질적 인벤토리
첫 세션부터 오늘까지 총 60일이 지났습니다. 60일 중 58일은 활발하게 활동했습니다. 즉, 이틀은 커밋(commit)이 없었으며, 그 이유는 나머지 삶이 간신히 유지되고 있었기 때문입니다. 이 기간 동안 리포지토리(repo)에는 제 이름이 붙은 **984개의 커밋 (commits)**이 쌓였습니다. 이는 업무일 기준 하루 평균 16개의 커밋을 기록한 수치입니다. 이 날들은 단순히 코드만 작성하는 날이 아니라, 회계 업무, 부모님의 전화, 팀 내 중재, 그리고 모두가 잠든 밤에 SQL을 다시 읽어야 했던 밤들이 포함된 날들입니다.
파일 시스템에 따르면, node_modules, .next, .claude를 제외하고, 누구 앞에서도 다시 실행해 보일 수 있는 find 명령어로 측정했을 때 오늘 기준 131,628줄의 TypeScript, TSX, JS, JSX 코드가 존재합니다. 규모를 짐작해 보자면, 지난 3월 저는 기술 부속서에 **생산된 코드 줄 수(lines produced)**를 기준으로 맞춤형 개발 비용을 청구하는 유럽의 상업용 ERP 발행사와 5자리 수(만 단위) 규모의 계약을 체결했습니다. 해당 계약은 현재 환불 협상 중입니다.
이 방대한 코드 덩어리 안에는 74개의 작성된 아키텍처 결정 (architecture decisions), 276개의 Supabase 마이그레이션 (migrations), .claude/rules/ 내의 18개 프로젝트 규칙 (project rules), docs/sessions/에 기록된 44개의 세션 (sessions), 그리고 별도의 폴더에 있는 67개의 DEV.to 기사 초안 (article drafts)(이 중 24개가 현재 당신이 읽고 있는 것을 포함하여 발행됨)이 들어 있습니다. AI 파일럿 교리(AI piloting doctrine)는 이 기간 동안 9개 버전 — v0.2, v0.3, v0.3.1, v0.3.2, v0.3.3, v0.4, v0.4.1, v0.6, v0.7 —을 거쳤으며, 각 버전은 새로 문서화된 사실이나 공개적인 철회에 근거하여 고정되었습니다. v0.5 버전이 건너뛰어진 것은 5월 중순의 경험적 고르곤(gorgon) 세션 이후에 인정된 것이지, 그냥 얼버무리고 넘어간 것이 아닙니다.
저는 이 수치들로 인상을 남기려고 쓰는 것이 아닙니다. 이 수치들이 없다면 다음에 이어질 내용이 성립되지 않기 때문이며, 이 실천 과정 전체가 이 수치들이 겉으로 보이는 것과 같은 의미를 담고 있지 않다는 것을 증명해 왔기 때문에 쓰는 것입니다.
효과적이었던 것
세 가지 메커니즘만이, 오직 세 가지만이 언급될 가치가 있습니다. 각 메커니즘은 특정 날짜와 함께 시작된 계기가 있으며, 수익을 내기 전 각각 학습 비용을 지불했습니다.
수정하기 전에 거짓임을 증명하라 (Falsify before fixing). 5월 6일 오후 초입, 그날 오전 세 건의 등록이 입력되었음에도 당일 등록 카운터가 0으로 표시된다는 Sentry 경고가 나타났습니다. 프로토콜을 따르기도 전에 반사적인 반응이 먼저 나왔고, 제 손은 이미 키보드 위에 있었습니다. _"캐시가 무효화되지 않았음(The cache isn't invalidated)"_이라고 커밋(commit)하고 배포(deploy)했습니다. 30분 후 롤백(rollback) — 버그는 여전히 남아 있었습니다. 90초간의 grep 탐색만으로도 드러났을 진짜 원인은, 등록 파이프라인 어디에도 cache_invalidate 호출이 존재하지 않는다는 것이었습니다. 오래된(stale) 캐시가 아니라, 아예 존재하지 않는(absent) 것이었습니다. 어떤 수정 이전에 검증 가능한 가설을 요구하는 텍스트 규칙은 이미 3주 동안 CLAUDE.md에 존재하고 있었습니다. 단지 그날 저는 그 규칙을 따르지 않았을 뿐입니다. 버그 발견과 커밋 사이의 실행 과정을 방해하는 장치가 없었기 때문입니다. 열흘 후, falsify-before-fix 스킬이 커밋되었습니다. 이는 더 이상 텍스트 규칙이 아니라, fix, bug, _doesn't work_와 같은 키워드가 입력될 때 로드되어 단 한 줄의 코드가 작성되기 전에 프로토콜을 강제하는 실질적인 스위치(material switch)가 되었습니다. 압박 속에서 유지되지 않는 텍스트 규칙과 세션을 물리적으로 중단시키는 실질적인 장치 사이의 간극이 바로 경건한 교리와 운영 가능한 교리를 가르는 지점입니다. 기록된 44번의 세션 전체를 통틀어, 저는 이 스킬이 '수정 후 롤백' 사이클을 방지했던 사례를 최소 12건 이상 꼽을 수 있습니다. 상류(upstream)에서의 510분 탐색은 하류(downstream)에서 절약되는 3090분의 가치가 있으며, 이는 주당 2~3개의 버그가 발생하는 상황에서 매달 꼬박 하루를 회복하는 결과로 이어집니다.
Live, Snapshot, Cache. 4월 말, 저는 3개월 동안 미뤄왔던 신뢰할 수 있는 원천 데이터(source-of-truth) 감사를 실시했습니다. 저는 이전에는 결코 교차 검증되지 않았던 두 개의 쿼리를 대조했습니다. 한 학생의 기록을 보니, contacts.montant_total 컬럼에는 2024년 어딘가에 입력된 이후 한 번도 수정되지 않은 1,159유로가 기록되어 있었습니다. 하지만 실시간으로 계산된 할부금의 실제 합계는 2,262유로였습니다. 단 하나의 기록에서 1,000유로의 차이가 발생했음에도 그 어떤 경고도 울리지 않았습니다. 저는 grep 범위를 넓혔고 — 560개의 연락처가 동일한 상태였으며, 일부는 수천 유로씩 차이가 났습니다. 이 값은 매일 재무 대시보드에서 읽혔고, 실시간으로 계산되어야 했음에도 불구하고 불변의 과거 사실처럼 취급되었습니다. 이 과정에서 도출된 규칙은 모든 마이그레이션(migration)이 검토 단계로 넘어가기 전, 새로 저장되는 모든 컬럼에 대해 세 가지 질문을 부과합니다. 1. 이 값은 상위 소스(upstream sources)와 함께 변화해야 하는가? 2. 실시간 계산(on-the-fly calculation)이 성능 측면에서 허용 가능한가? 3. 캐시(Cache)를 선택할 경우 선언된 갱신 메커니즘은 무엇인가? 이 세 가지 질문에 대한 명시적인 답변 없이는 어떠한 새로운 유도 가능 컬럼(derivable column)도 스키마(schema)에 진입할 수 없습니다. 이 규칙은 근본적인 수준에서 해당 부류의 문제를 차단했으며, 3개월 후 감사에서는 더 이상 동일한 유형의 사고가 발견되지 않습니다. 이후의 감사에서 이러한 결함이 발견되지 않는다는 사실이야말로, 수정된 560개의 기록보다 더 진정한 성공을 의미합니다.
요약보다 파일 시스템 (Filesystem over summary). R2는 저를 잠 못 이루게 했던 발견 이후 4월에 출시되었습니다. 매일 아침 상황을 파악하기 위해 다시 읽던 backlog.md가 더 이상 사실이 아닌 내용을 담고 있었기 때문입니다. 악의가 아니라 구조적인 문제였습니다. 요약 파일은 빠르게 생성되지만 유지 관리는 느리게 이루어집니다. 마치 갱신되지 않는 캐시처럼 조용히 노후화됩니다. 저는 이미 기술 부채 인벤토리 (technical debt inventory)에서 이를 측정했습니다. INVENTAIRE.md가 발표한 내용과 git log가 기록한 작업 내용 사이에 11일의 격차가 있었지만, 경고가 설정되어 있지 않았기에 아무런 알람도 울리지 않았습니다. R2 이후, 모든 상태 확인은 작성된 요약을 읽기 전에, 파일 시스템을 대상으로 git log --since='7d', ls docs/adr/ | wc -l, git status --porcelain을 실행하는 것으로 시작합니다. 작성된 요약은 결코 권위(authority)가 될 수 없습니다. 파일 시스템이 언제나 권위입니다. 왜냐하면 파일 시스템은 작성자의 태만으로 인해 거짓을 말하지 않는 유일한 산출물(artifact)이기 때문입니다. 이 규칙 덕분에 저는 매주 선의로 내뱉으려 했던 잘못된 주장을 피할 수 있습니다.
효과가 없었던 것들
각기 다른 상처를 남긴 세 가지 함정이 있습니다. 제가 이를 언급하는 이유는, 제가 읽어본 Claude Code에 관한 그 어떤 DEV.to 게시물도 이 문제들을 인정하지 않기 때문이며, 이것이야말로 실제 실무와 해커톤 식의 사고 리더십 (thought leadership)을 구분 짓는 핵심이기 때문입니다.
에이전트의 과잉 엔지니어링 (over-engineering) 본능. 5월 중순, 나는 Claude Code에게 리드(leads)를 대상으로 한 첫 접촉 아웃리치(outreach)를 자동화해 달라고 요청했습니다. 이는 개발 하루면 충분할 간단한 비즈니스 케이스였습니다. 첫 번째 제안은 6개의 파일로 구성되어 도착했습니다: 전용 app_config 테이블, 점진적인 발송 워밍업 (progressive send warm-up), 멱등성 (idempotence)을 위한 결정론적 해시 (deterministic hash), 그리고 발송 빈도를 감독하기 위한 관리자 대시보드까지 포함되어 있었습니다. 나는 _"더 단순하게(Simpler)"_라고 입력했습니다. 두 번째 제안은 워밍업은 제외했지만, 설정 테이블과 해시는 그대로 유지했습니다. "훨씬 더 단순하게(Even simpler)." 세 번째 제안에 이르러서야 마침내 정직한 6개의 파일—크론 라우트 (cron route), 재사용 가능한 기존의 email_outbox, 그리고 약속대로 하루 만에 끝낼 수 있는 개발 분량—에 도달했습니다. 아무도 요청하지 않은, 너무 비싼 기능을 두 번이나 쓰지 않도록 에이전트를 재조정(recalibration)하는 데 세 번의 라운드가 필요했습니다. 함정은 에이전트가 복잡한 솔루션을 제안한다는 점이 아닙니다. 그 솔루션을 너무나 잘 제안해서 당신이 첫 번째에 바로 수락하게 만든다는 점이며, 아무도 요청하지 않은 app_config 테이블이 목적 없는 가구처럼 스키마(schema) 안에 자리 잡고 있다는 사실을 일주일 뒤에나 깨닫게 된다는 점입니다. 해당 세션 이후 절약 (Parsimony)이라는 규칙 R11이 명문화되었지만, 아직 falsify-before-fix 기술과 같은 실질적인 장치는 갖추지 못했습니다. 이는 v0.7 이전에 해결하고 싶었던 미결된 교리적 부채 (doctrinal debt)입니다.
내가 직접 작성하는 요약본들의 조용한 표류. R2는 읽는 쪽의 관점에서 이 문제를 설명합니다. 즉, 요약본은 경고 없이 노후화된다는 것입니다. 제가 이해하는 데 시간이 걸렸던 점은, 제가 생성하는 요약본들이 먼저 표류한다는 사실이었습니다. 교리(doctrine) 자체가 R2에 대해 죄를 지었습니다. 몇 주 동안 Rembrandt를 정의하기 위해 _"35,000행"_이라고 공표해 왔기 때문입니다. 실제 작업량은 이미 오래전에 이를 초과했습니다. 그 수치는 한 번 투영되었고, 연속되는 버전들에 걸쳐 여섯 번 반복되었을 뿐, 다시 측정된 적이 없었습니다. 네 번째 외부 검토자가 이를 다시 측정했을 때, 수치는 118,000행으로 폭발적으로 늘어났고, 이 글을 쓰는 시점에는 131,000행에 도달했습니다. 그 교리는 갱신(refresher) 기능이 없는 _Cache_가 자신의 설명에 투영된 상태였습니다. 이 철회 내용은 v0.4.1 릴리스 노트에 기록되었습니다. 왜냐하면 자신의 잘못된 수치를 철회하지 않으면서 체계적인 허위 사실 유포에 관한 교리를 작성하는 것은, 그 교리가 비판하는 바로 그 안주(complacency)가 될 것이기 때문입니다. 뼈아픈 교훈은 이것입니다: 만약 당신의 교리가 스스로의 규칙을 견뎌내지 못한다면, 틀린 것은 규칙이 아니라 아직 시작조차 하지 않은 당신의 적용(application)입니다.
저와 동일한 메모리를 로드하지 않는 서브 에이전트(Sub-agents). 5월 18일의 커밋 3756e63은 자동 전송 리팩토링(autosend refactor)에 대해 브리핑을 받은 ERP 서브 에이전트에 의해 main 브랜치로 직접 푸시되었습니다. "중요한 커밋을 하기 전에는 항상 git branch --show-current를 확인하라"는 사용자 범위(user-scope) 피드백은 5월 14일부터 존재했습니다. 저는 이미 동일한 종류의 장애를 통해 그 학습 비용을 지불한 상태였습니다. 하지만 그 피드백은 사용자 범위(user-scope) 메모리에 머물러 있으며, ERP 서브 에이전트는 저로부터 이행적(transitively)으로 상속되지 않는 프로젝트 범위(project-scope) 메모리를 로드합니다. 파일럿 에이전트는 제가 아는 것을 알지 못합니다. v0.7에서 수정된 규칙 R9은 이 결함을 해결합니다. 이제 30분 이상의 모든 서브 에이전트 브리핑은 핵심 피드백(load-bearing feedbacks)을 반드시 _인라인(inline)_으로 포함해야 합니다. 저를 대신해 이를 수행해 줄 이행적 메커니즘은 없기 때문입니다. 하지만 수정 사항이 사고 자체를 지우지는 못합니다. 3시간 동안, 하나의 자율 에이전트는 교리를 준수하고 있다고 믿으며 리포지토리(repo) 내에서 작동했지만, 실제로는 교리의 절반만을 로드한 상태였습니다. 이는 나중에 git log를 확인하여 커밋이 발생한 브랜치를 발견하기 전까지는 보이지 않는 일종의 침묵하는 실패(silent failure)입니다. 다시 한번, 권위로서의 파일 시스템(Filesystem as authority) 문제입니다.
제가 다르게 했을 일들
만약 제가 지금 알고 있는 것들을 바탕으로 오늘 Rembrandt를 다시 시작해야 한다면, 저는 딱 세 가지만 다르게 할 것입니다. 나머지는 — 스키마(schema), 라우트(routes), 인증(auth), 대시보드(dashboard), 계획(planning), 근태(attendance), 가치 평가(valuation), 재무(finance) — 모두 원래 방식과 거의 동일하게 진행할 것입니다. 프로젝트의 본질이 구조를 강제했기에 제가 움직일 수 있는 여지는 거의 없었을 것이기 때문입니다.
저는 5월 6일의 사고 이후가 아니라, 첫날부터 falsify-before-fix 기술을 설치했을 것입니다. 당시에는 그런 도구가 없었다는 것을 압니다. 하지만 오늘날 그러한 장치가 존재한다면, Claude Code를 시작하는 모든 1인 개발자의 첫 번째 반사 신경은 첫 번째 기능을 구현하기 전에 doctrine-counterpart 리포지토리를 클론하고 프로젝트에서 ./install.sh를 실행하는 것이어야 합니다. 이 규칙의 학습 비용은 첫 번째 강제 사용 시 발생하는 비용입니다. 즉, 하루 안에 익숙해질 30초간의 마찰(friction)입니다. 이 기술이 없을 때 치러야 하는 비용은 첫 한 달 동안 매주 발생하는 롤백(rollback)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기