Claude Code 개인개발에서 에이전트·CI·인간 역할 분리

시작하며
누구나 자신의 Cloudflare 계정에 배포해 사용할 수 있는, 서버 관리가 필요 없는 가족용 사진 앱 EdgePhotos를 개인적으로 개발하고 있습니다. Workers·D1·R2를 사용하고 있으며, 구현에는 Claude Code를 사용하고 있습니다.
리포지토리:
https://github.com/sano-suguru/edgephotos

앱 설계에 대해서는 별도의 글에 적었습니다.
https://zenn.dev/nossa/articles/b7d42369d7bfde
EdgePhotos에서는 구현을 에이전트에게 맡기는 한편, 그 결과가 올바른지 확인하는 수단은 따로 두고 있습니다.
예를 들어 HEIC 대응 PR에서는 Claude Code가 몇 번이나 “CI는 통과했다”고 보고했지만, 실제 GitHub Actions는 실패하고 있었습니다.
또 다른 경우에는 명령어 출력을 요약하는 rtk hook 때문에 lint 오류가 보이지 않게 된 적도 있었습니다.
현재는 대체로 다음과 같이 나누고 있습니다.
| 대상 | 담당 |
|---|---|
| 구현 방법을 생각하기 | 에이전트 |
| 필요한 테스트를 생각하기 | 에이전트 |
| 타입·lint·테스트·build의 합격 여부 | CI |
| 실제 환경·실기기에서의 확인 | 나 |
| 무엇을 작업 완료로 볼 것인가 | 나 |
업무에서도 AI 코딩 에이전트를 위한 harness나 CI 주변 정비에 관여하고 있습니다. 이 글에서는 개인 개발에서 실제로 시험해 본 것과, 도중에 바꾼 것을 적습니다.
큰 작업에서는 목적과 도달 상태를 전달했다
큰 작업에서는 Claude Code에 세부 구현 절차를 길게 전달하는 일은 별로 하지 않았습니다.
대신,
- 왜 하는지
- 무엇을 달성하고 싶은지
- 무엇이 일어나면 안 되는지
를 먼저 전달했습니다.
다만, 구현 방법을 완전히 맡겨 버린 것은 아닙니다.
구현을 시작하기 전에는 superpowers의 brainstorming이나 grilling skill을 사용해, 에이전트에게 질문을 받으면서 계획을 다듬었습니다.
예를 들어 사진 가져오기 검증에서는 다음과 같이 의뢰했습니다.
EdgePhotos の photo ingestion を、実際の iPhone / Android camera roll を前提に検証・改善してください。
目的は機能追加ではなく、「現実のスマホ写真を大量に入れたとき、どこで壊れるかを測定し、v1 に必要な問題だけ直す」ことです。다른 날에는,
사진의 손상·소실·의도하지 않은 공개로 이어지는 문제를 고친다
라는 목적으로 부탁했습니다.
이때 제가 파악하지 못했던 버그가 발견되었습니다.
사진을 완전히 삭제할 때 R2 쪽 삭제만 실패하면, 화면에는 보이지 않는 D1의 행이 사진 해시를 가진 채 남아 있었습니다.
그 결과 같은 사진을 다시 선택해도 “저장됨”으로 판정되어, 다시 가져올 수 없었습니다.
“이 기능을 고쳐 달라”처럼 변경 위치를 지정했다면 이 문제는 대상이 되지 않았을지도 모릅니다.
그 이후로 EdgePhotos에서는, 큰 조사나 개선을 부탁할 때 변경 위치뿐 아니라 “어떤 실패를 피하고 싶은가”도 의뢰에 포함하도록 했습니다.
AGENTS.md에 무엇을 적었나
첫 커밋에는 코드보다 먼저 AGENTS.md를 넣었습니다.
CLAUDE.md의 내용은 @AGENTS.md 한 줄만 두어, Claude Code 이외의 도구에서도 같은 내용을 참조할 수 있게 했습니다.
AGENTS.md에는 명령어 목록이나 세세한 코드 작성법은 거의 적지 않았습니다.
명령어는 package.json에 있습니다. 코드 작성법은 linter 설정을 보면 알 수 있습니다.
적은 것은 코드만 읽어서는 알기 어려운 조건들입니다.
예를 들면,
- 촬영한 그대로의 사진 파일을 변경·재인코딩·덮어쓰지 않는다
- 사진을 두는 R2 bucket을 공개하지 않는다
- R2의 파일명에 원본 파일명·이메일 주소·촬영일을 포함하지 않는다
- 공유 링크에서는 촬영한 그대로의 파일을 배포하지 않는다
와 같은 것들입니다.
이런 것들은 지키지 않아도 기능적으로는 동작해 버릴 수 있습니다.
사진을 가볍게 하려고 재인코딩해도 일반적인 표시 테스트는 통과합니다. R2 bucket을 공개해도 앱의 단위 테스트는 실패하지 않습니다.
채택한 구성에 대해서도 적었습니다.
Preact, Hono, D1, R2, Cloudflare Access 등을 나열하고, 변경할 경우에는 먼저 Decision Log를 갱신하도록 했습니다.
테스트를 어디까지 쓸지도 정했습니다.
회귀 테스트를 필수로 한 것은
- 인증
- 사진 가져오기
- 중복 판정
- 삭제와 복원
- 공유 링크 취소
- DB migration
- backup / restore
입니다.
반면 겉모습만 바뀌는 변경에는 형식적인 테스트를 굳이 늘리지 않아도 된다고 했습니다.
개인 개발에서는 늘린 테스트를 나중에 고치는 것도 자신입니다.
그 때문에 사진을 잃어버리거나, 새거나, 복구 불가능해지거나 하는 문제로 이어지는 부분을 우선해 테스트했습니다.
실데이터 취급에 대해서도 적었습니다.
가족 사진이나 실제 GPS 정보를 fixture로 두지 않는 것에 더해,
디버그 정보를 AI에 전달할 경우에도 secret, 서명된 query, 실제 사진을 포함하지 않는다
라고 했습니다.
에이전트가 버그를 조사할 때 로그나 설정을 읽는 경우가 있기 때문입니다.
“지금은 쓰지 않는다”의 쓰는 방식을 바꿨다
처음의 AGENTS.md에서는 Cloudflare Queues·Durable Objects·Cron 등, “지금은 넣지 않을 것들”을 금지에 가까운 형태로 나열했습니다.
그 뒤, 중간에 멈춘 upload가 R2에 남긴 파일을 삭제하지 않는 이유로 PR에,
Cron이나 Queues가 필요해지므로 넣지 않았습니다
라고 적혔습니다.
제가 피하고 싶었던 것은 필요성이 낮은 구성 요소를 늘리는 것이었습니다.
Cron을 사용하지 않는 것 자체가 목적은 아니었습니다.
이 경우 금지에 가까운 표현이 “사용하지 않는 것 자체가 제약”으로 해석된 것처럼 보였습니다.
그래서 금지가 아니라 도입 조건으로 다시 썼습니다.
これは禁止リストではなく、導入条件です。上の技術を避けること自体は目的ではありません。
導入するときは、次の 3 点を根拠に判断し、`docs/decisions.md` に記録します。
1. 現在の構成で何が足りないか
2. その技術で何が具体的に良くなるか
3. 増える運用・障害・保守のコストEdgePhotos에서는 그 후 “쓰면 안 된다”가 아니라, “어떤 경우라면 넣어도 되는가”를 적게 되었습니다.
CI의 합격 여부를 Claude Code의 보고로 판단하는 것을 그만뒀다
HEIC 대응 PR에서 Claude Code는 “CI는 통과했습니다”라고 3번 보고했습니다.
실제로는 그 사이의 CI가 4번 모두 실패하고 있었습니다.
로컬에서 pnpm check와 e2e가 통과한 것을, CI가 통과한 것으로 취급하고 있었던 듯합니다.
실패 원인은 e2e의 나누는 방식이었습니다.
처음에는,
WebKit이면 HEIC를 표시할 수 있다
는 전제로, 브라우저 종류에 따라 테스트를 나누고 있었습니다.
로컬의 macOS WebKit에서는 HEIC를 표시할 수 있었지만, CI의 Linux 위 WebKit에서는 표시할 수 없었습니다.
그래서 현재는 브라우저 이름이 아니라, 그 자리에서 실제로 HEIC를 표시할 수 있었는지 여부로 테스트를 나누고 있습니다.
lint에서도 비슷한 일이 있었습니다.
명령어 출력을 짧게 요약하는 rtk hook을 넣어 둔 상태에서, Biome가 오류를 내고 있었는데도 에이전트에 전달된 요약은,
No issues found였습니다.
그대로 lint 오류를 포함한 코드가 커밋에 들어갔습니다.
이 두 가지가 일어난 뒤에는 타입 체크·lint·테스트·build의 합격 여부에 대해 Claude Code의 자연어 보고를 확인 수단으로 삼지 않기로 했습니다.
GitHub의 ruleset에서 CI의 check와 e2e를 required status check로 두고 있습니다.
이 두 개가 성공하지 않으면 main에 merge할 수 없습니다.
일반적인 흐름은 다음과 같이 하고 있습니다.
변경 내용에 따라 실제 환경이나 실기기에서의 확인이 필요하면, merge 전에 제가 따로 확인합니다. 문제가 있으면 에이전트에게 되돌리고, 확인한 내용은 docs/verification.md에 남겨 두고 있습니다.
에이전트에게는,
- 어떤 변경에 어느 정도 테스트를 쓸지
- 환경에 따라 동작이 달라지는 기능을 어떻게 테스트할지
- 실제 환경에서 무엇을 확인할지
를 생각하게 하고 있습니다.
한편,
- 타입 체크
- lint
- migration과 schema의 불일치
- 단위 테스트와 통합 테스트
- build
- Chromium과 WebKit의 e2e
의 합격 여부는 CI에 맡기고 있습니다.
CI에서는 확인할 수 없었던 것
CI가 통과해도, 본番과 같은 구성이어야만 알 수 있는 것은 남아 있었습니다.
EdgePhotos의 로컬 환경과 CI에서는 R2로의 upload를 개발용 서버가 대신 받아줍니다.
그 때문에 본番의 R2 CORS 설정에 문제가 있어도, 테스트로는 검출할 수 없습니다.
실제로 upload용 서명된 URL에서 쓰는 header를 늘렸을 때, 운영 절차에 적어 두었던 CORS 설정 예시에 추가를 빠뜨린 적이 있었습니다.
CI는 모두 통과했습니다.
이처럼 본番과 같은 구성이어야만 확인할 수 있는 부분은 검증 환경에서 제가 직접 확인하고, 결과를 docs/verification.md에 남기고 있습니다.
자동화할 수 있는 것은 e2e로 돌리고 있습니다. 한편, R2의 CORS 설정이나 실기기 의존 동작까지 본番과 같은 조건으로 재현하기 위한 테스트 환경은 만들지 않았기 때문에, 그 부분은 검증 환경에서 확인하고 있습니다.
문서에서는 정확성과 읽기 부담을 함께 보도록 했다
설계서, 운영 절차, 검증 기록 등의 문서도 에이전트가 작성하게 하고 있습니다.
여기서는 사실관계가 틀렸다기보다, 정확하게 쓰려 할수록 설명이 겹겹이 쌓여 읽는 사람의 부담이 커지는 일이 있었습니다.
“다만 ~인 경우를 제외하고”
“~에 대해서는 다른 문서를 참조”
와 같은 단서가 늘어나거나, 한 문장에 전제와 예외를 우겨 넣기도 합니다.
하나하나의 기술은 필요해도, 그것이 누적되면 독자는 “결국 여기서 무엇을 판단하면 되는가”를 따라가기 어려워집니다.
README, 설계서, 운영 절차처럼 읽히기 위한 문서는 첫날 32KB에서 10일 만에 166KB까지 늘었습니다.
이와 별개로, 판단 기록과 검증 기록이 있습니다. 이쪽은 기록으로 남기는 것이 목적이므로, 분량이 늘어나는 것 자체는 그다지 문제로 보지 않고 있습니다.
Claude가 쓴 문서를 GPT에게도 읽게 하면, Claude 자신에게 다시 보게 했을 때와는 다른 지적이 나오는 경우가 있었습니다.
그래서 중간부터는 GPT에게 Markdown의 읽기 쉬움을 정기적으로 검토하게 했습니다. 봐 달라고 한 것은 내용의 정확성이 아니라, 문장의 구조와 읽기 쉬움입니다.
예를 들면,
- 한 문장에 여러 조건이나 예외를 우겨 넣지 않기
- 괄호 안에 긴 설명을 넣지 않기
- “§12를 참조”처럼 번호만으로 다른 곳을 가리키지 않기
- control plane, consumer, contract 같은 추상어를 그대로 일본어 조사로 이어 붙이지 않기
같은 점입니다.
모델을 바꾸기만 한다고 항상 좋아지는 것은 아니지만, 같은 문장에 다른 관점을 넣는 방법으로 사용하고 있습니다.
다만, 문장을 짧게 하면 된다는 뜻도 아니었습니다.
어떤 다시쓰기에서는 읽히기 위한 문서가 114KB에서 111KB로 줄어드는 데 그쳤습니다.
또 줄이는 과정에서,
upload의 잔재가 늘어날수록, 나중에 지워도 되는 것인지 판정하는 것도 어려워진다
라는 한 문장이 빠졌다가, 나중에 다시 넣은 적이 있습니다.
이 문장은 조작 절차 그 자체는 아니지만, 왜 잔재를 빨리 정리해야 하는지를 판단하는 근거가 됩니다.
그래서 지금은 문장을 줄일 때 주로 다음 점을 보고 있습니다.
- 같은 내용을 다른 곳에서도 설명하고 있지 않은가
- 예외를 본문에 둘 필요가 있는가
- 읽는 사람이 그 자리에서 판단하기 위해 필요한 정보인가
- 지웠을 때 읽는 사람의 행동이나 판단이 달라지는가
반대로, 배경으로 기록해 두고 싶은 내용은 Decision Log나 검증 기록으로 보내고, README나 운영 절차에는 가능한 한 가져오지 않으려 하고 있습니다.
문장의 규칙을 세세하게 정한 적도 있습니다.
예전에는 문장의 길이나 표기 통일 방식까지 정해 두었습니다.
그러자 적어도 이 프로젝트에서는 그 규칙이 꽤 기계적으로 적용되어, 어떤 문장도 비슷한 리듬이 되었습니다.
현재는,
- 짧게 쓸 것
- 맞추기 위해 기존 문장을 고쳐 쓰지는 않을 것
정도로만 두고 있습니다.
어디까지 설명을 남길지는 지금도 시행착오 중입니다. 문서량이 계속 늘고 있는 것 자체도 아직 해결하지 못했습니다.
넣어 두었던 hook을 일부 제거했다
Claude Code에서는 plugin, skill, hook 등을 추가해서 사용하고 있습니다.
EdgePhotos 개발에서도 구현 전에 상담하거나 UI를 확인하거나 명령어 출력을 요약하는 데 사용해 왔습니다.
그중에는 사용법을 바꾼 것도 있습니다.
앞서 lint error를 숨기던 hook은 명령어 출력을 요약하는 rtk였습니다.
EdgePhotos를 시작하기 전부터, 에이전트가 읽는 토큰을 줄이려는 목적으로 넣어 두었습니다.
한편 EdgePhotos에서는 적어도 pnpm check의 출력에 대해서는, 요약 때문에 에러 정보가 빠지는 쪽이 문제가 되었습니다.
현재는 pnpm check를 요약하지 않고 실행하며, 종료 코드와 원래 출력을 보고 있습니다.
plugin, skill, hook을 어디까지 넣을지는 아직 정하지 못했습니다.
한 번 넣은 것을 그대로 계속 쓰는 것이 아니라, 이번처럼 문제가 생긴 것은 용도를 좁히거나 제거하고 있습니다.
맺음말
EdgePhotos에서는 Claude Code에 구현의 상당 부분을 맡기고 있습니다.
다만 구현을 맡기는 것과, 그 결과의 합격 여부나 완료를 판단하는 것은 분리하고 있습니다.
큰 작업에서는 목적과 도달 상태를 넘기고, 구현 방법과 필요한 테스트는 에이전트와 함께 조율했습니다.
코드만 봐서는 알기 어려운 제약은 AGENTS.md에 두고 있습니다.
타입, lint, 테스트, build의 합격 여부는 GitHub Actions와 ruleset에 맡기고 있습니다.
본番과 같은 구성에서만 확인할 수 있는 것은 제가 직접 확인해서 기록하고 있습니다.
또한 에이전트를 위해 추가한 장치도 고정하지 않고, 이번 rtk처럼 정보를 떨어뜨리는 원인이 된 것은 사용법을 바꾸었습니다.
아직 정하지 못한 것도 있습니다.
문서량은 계속 늘고 있고, plugin, skill, hook을 어디까지 더할지도 아직 모색 중입니다.
지금으로서는 EdgePhotos에서 “에이전트에게 무엇을 시킬 것인가”와 “그 결과를 무엇으로 확인할 것인가”를 나눠서 생각하고 있습니다.
출처
- 원문: https://zenn.dev/nossa/articles/250bb1218db572 (2026-09-26 · のさ)