Night Worker
팀원들이 퇴근한 밤에도, AI가 일합니다.
왜 만들었는가
팀에서 Claude Code Max Plan을 쓰고 있었다. 그런데 막상 업무 시간에 제대로 활용하는 사람이 많지 않았다. 세션 시간 제한이 있어서 길게 맡기기 어렵고, 중간에 개입해야 하는 순간이 많아 흐름이 끊겼다. 결국 "보조 도구"로만 쓰이고 있었고, 맥스 플랜에 넣어둔 토큰은 상당 부분 소진되지 않은 채 매달 리셋됐다.
팀에는 반복 업무도 많았고, 특히 테스트 작성이 병목이었다. 새 기능을 붙이고 나면 테스트를 짜야 하는데, 그게 쌓이면 야근으로 이어졌다. "이런 작업을 밤 사이에 AI가 처리해주면 어떨까?" 하는 생각이 들었다.
누가 시킨 게 아니었다. 팀원들을 돕고 싶었다. 퇴근 후 잠자리에 들면서 태스크를 등록해두고, 아침에 출근하면 결과가 나와 있는 경험을 만들어주고 싶었다. 그래서 직접 만들기 시작했다.
Night Worker가 하는 일
Night Worker는 Claude Code CLI를 크론 스케줄로 자동 실행하는 플랫폼이다. 대시보드에서 태스크를 등록하면, 지정한 시간에 Claude Code가 실제 코드베이스 위에서 작업을 수행하고 결과를 기록한다. 팀원이 자고 있는 사이에도, AI는 리뷰하고, 구현하고, 분석한다.
설치형으로 설계했다. SaaS가 아니라 개인 PC에 설치해서 쓴다.
기존에 쓰던 Claude Code를 그대로 활용하기 때문에 API 키나 권한 설정이 따로 필요 없다.
pm2 start 한 줄로 배포가 끝난다.
실행 모드
Auto
--permission-mode auto — 도구 사용을 자율 승인. 코드 읽기·쓰기·커밋까지 끝까지 수행 (Pro/Max 플랜 필요)
권한 무시
--dangerously-skip-permissions — Pro/Max 없이도 자동화 실행. 신뢰할 수 있는 프롬프트에만 사용
제한
--allowedTools — Read/Write/Edit/Bash 등 허용 도구를 체크박스로 개별 선택
프롬프트는 프리셋으로 관리한다. 자주 쓰는 지시문(코드 리뷰, 테스트 작성, 문서화 등)을 설정에 저장해두고, 작업 등록 시 드롭다운에서 선택해 채운 뒤 필요하면 그 자리에서 수정한다. 작업 타입을 코드에 박아두는 대신, 팀의 실제 사용 패턴이 프리셋으로 축적되도록 했다.
실제 화면
태스크 등록 화면
실행 로그 뷰어
script.service.spec.ts에서 타임아웃 관련 실패를 확인했습니다. 수정하겠습니다.architecture
graph TD
subgraph "Frontend · Astro + React"
WEB[Web Dashboard]
LOG_VIEW[Log Viewer]
end
subgraph "Backend · NestJS"
API[REST API]
SCHED[Scheduler Service]
RUNNER[Runner Service]
LOG_SVC[Log Service]
STREAM[Stream Parser]
end
subgraph "Storage"
SQLITE[(SQLite + WAL)]
JSONL[JSONL Files]
end
subgraph "External"
CLAUDE[Claude Code CLI]
MCP_SRV[MCP Servers]
end
WEB --> API
API --> SCHED
SCHED -->|cron trigger| RUNNER
RUNNER -->|spawn| CLAUDE
CLAUDE --> MCP_SRV
CLAUDE -->|stream-json| STREAM
STREAM --> LOG_SVC
LOG_SVC --> SQLITE
LOG_SVC --> JSONL
LOG_SVC -->|SSE| LOG_VIEW
Frontend는 Astro + React로 구성된 웹 대시보드다. 태스크 등록·조회와 실시간 로그 뷰어를 제공한다.
Backend는 NestJS로, Scheduler가 크론 트리거를 발생시키면 Runner가 Claude Code CLI를
child_process.spawn으로 실행한다.
CLI 출력은 NDJSON 스트림으로 실시간 파싱되어 SQLite와 JSONL 파일에 동시 기록된다. 프론트엔드 로그 뷰어는 SSE로 연결되어 실행 중인 태스크의 로그를 즉시 확인할 수 있다. Storage는 DB 서버 없이 SQLite를 사용하며, WAL 모드로 동시 읽기·쓰기를 처리한다.
기술적으로 재밌었던 것들
Claude Code CLI를 프로그래밍으로 제어하기
Claude Code는 원래 대화형 CLI다. 이걸 비대화형으로 제어하려면
child_process.spawn으로 호출하면서
--output-format stream-json 플래그를 붙이면 된다.
그러면 구조화된 NDJSON 스트림이 stdout으로 나온다.
실행 모드는 세 가지 플래그로 스위칭된다 —
--permission-mode auto,
--dangerously-skip-permissions,
--allowedTools.
모델(sonnet/opus/haiku), 비용 한도, MCP 설정도 모두 커맨드라인 인자로 전달한다.
코드 변경이 포함된 작업은 체크박스 하나로 git worktree 격리 실행을 켤 수 있어, 메인 브랜치를 건드리지 않고 밤 사이 실험할 수 있다.
처음엔 단순히 "CLI를 실행하는 것"이라고 생각했는데, 인자 조합으로 에이전트의 권한 범위와 행동 양식을 세밀하게 제어할 수 있다는 걸 알게 됐다. CLI가 사실상 프로그래머블한 에이전트 런타임이었다.
실시간 로그 파싱과 이중 기록
Claude Code의
stream-json 출력은 라인 단위 NDJSON이다.
스트림이 들어오는 즉시 라인별로 파싱해서 role을 기준으로 구조화한다.
assistant,
tool_use,
tool_result,
system — 각각 다른 형태로 저장된다.
기록은 두 곳에 동시에 한다. SQLite에는 쿼리 가능한 구조화 데이터로, JSONL 파일에는 원본 아카이브로. SQLite는 대시보드 검색과 필터링을 위한 것이고, JSONL은 나중에 재처리하거나 외부로 내보낼 수 있도록 남겨두는 것이다.
프론트엔드 로그 뷰어는 SSE로 연결된다. 태스크가 실행 중일 때 실시간으로 Claude의 생각과 도구 호출 과정을 볼 수 있다. "AI가 지금 무엇을 하고 있는지"를 실시간으로 보는 경험이 생각보다 흥미로웠다.
MCP로 외부 도구 연동
--mcp-config로 MCP 서버 설정을 넘기면 Claude Code가 외부 도구를 쓸 수 있다.
Night Worker에서는 글로벌 MCP 설정을 정의해두고,
allowedTools 패턴 매칭으로 태스크마다 사용 가능한 도구를 제한한다.
실제로 가장 유용했던 케이스는 Atlassian 연동이다. 코드 리뷰 태스크가 끝나면 발견한 이슈를 Jira에 자동으로 등록한다. 아침에 출근하면 Claude가 작성한 Jira 이슈가 올라와 있는 경험이 꽤 인상적이었다.
MCP 덕분에 "코드만 보는 에이전트"가 아니라 "팀 도구와 연결된 에이전트"가 됐다. 어떤 MCP 서버를 붙이느냐에 따라 Night Worker의 역할이 확장된다.
프리셋 기반 프롬프트 관리
초기 버전에는 review/implement/analyze 같은 태스크 타입을 코드에 박아두고 타입별 시스템 프롬프트를 주입했다. 그런데 실제로 팀이 쓰는 작업은 훨씬 다양했다 — 타입을 늘릴수록 경계가 애매해졌고, 정작 자주 쓰는 패턴은 프롬프트 안에 숨어 있었다.
그래서 타입을 걷어내고 프리셋으로 옮겼다. 자주 쓰는 지시문을 설정에서 관리하고, 작업 등록 시 드롭다운으로 불러와 채운 뒤 필요하면 그 자리에서 수정한다. "비대화형 실행이니 질문하지 말고 스스로 판단하라"는 기본 지시만 시스템이 보장하고, 나머지는 팀이 프리셋으로 누적한다.
코드가 단순해졌고, 무엇보다 팀의 실제 사용 패턴이 프리셋 목록에 드러나기 시작했다. "이런 프롬프트가 잘 먹힌다"는 암묵지가 설정 화면에 축적되는 건 예상하지 못한 효과였다.
설치형 아키텍처 — 중앙 서버 없이
SaaS로 만들 수도 있었지만 설치형을 선택했다. 이유는 단순하다 — 팀이 이미 Claude Code를 쓰고 있고, API 키 관리나 별도 권한 설정 없이 그걸 그대로 활용하고 싶었다. 중앙 서버를 두면 보안 이슈도 생기고, 팀마다 Claude 플랜이 다르면 복잡해진다.
배포는
pm2 start 하나로 끝난다.
Storage는 SQLite를 쓰기 때문에 DB 서버가 필요 없다.
WAL 모드를 켜서 스케줄러가 쓰는 동안 대시보드가 읽을 수 있게 했다.
동시성 제어도 필요했다. 여러 태스크가 같은 프로젝트에서 동시에 실행되면 충돌이 생긴다. 프로젝트 단위 lock과 글로벌 concurrency 제한을 함께 두어 안전하게 처리한다.
팀에서 쓰고 나서
맥스 플랜 토큰이 처음으로 제대로 소진되기 시작했다. 낮에 못 쓴 토큰을 밤 사이 Night Worker가 소진한다. AI 활용도가 눈에 띄게 올라갔다.
테스트 작성 병목이 상당 부분 해소됐다. 스킬 플러그인과 연계해서 새 기능 머지 후 테스트 생성 태스크가 자동으로 등록되도록 했고, 아침에 출근하면 PR이 올라와 있는 경우가 생겼다.
야근이 줄었다. 팀원들 반응도 긍정적이었다. "이게 진짜로 되는 거야?"라고 처음에 반신반의하던 사람이 며칠 후 "어제 등록해뒀는데 다 돼 있더라"고 말할 때가 제일 뿌듯했다.
돌아보며
"좋아서 만든 것"이 실제 팀 임팩트로 이어졌다. 지시를 받아서 만든 게 아니라, 팀원들을 돕고 싶다는 마음에서 시작했는데 결과적으로 팀에서 실제로 쓰는 도구가 됐다. 만드는 이유가 명확하면 결과물도 달라진다는 걸 다시 느꼈다.
동료들의 피드백이 계속 들어오고 있다. "이 태스크 타입도 추가해줄 수 있어?", "로그를 슬랙으로도 받을 수 있으면 좋겠는데." 처음 만든 것보다 지금이 훨씬 낫고, 지금보다 한 달 후가 더 나을 것 같다.
Night Worker는 현재진행형이다.