← 실험실 목록

Night Worker

Claude Code CLI 작업을 예약 실행하고 상태·로그·결과를 남기는 내부 자동화 도구입니다.

2026.03 ~ · Personal Project
NestJSAstroReactSQLiteClaude Code CLIMCPPM2

왜 만들었는가

코드 리뷰, 테스트 작성, 정적 분석처럼 반복할 수 있지만 시간이 오래 걸리는 작업이 개발 흐름을 자주 끊었다. 대화형 에이전트는 사람이 자리를 비우면 질문이나 권한 승인에서 멈췄고, 실행 이력도 여러 세션에 흩어졌다.

그래서 작업의 입력, 허용 도구, 실행 시각과 비용 한도를 미리 정하고, 진행 상태와 결과를 나중에 검토할 수 있는 실행 계층을 만들었다.

목표는 사람 없이 배포하는 자동화가 아니라, 사람이 자리를 비운 동안 반복 작업을 수행하고 검토 가능한 결과를 남기는 것이었다.


Night Worker가 하는 일

대시보드에 작업과 실행 시각을 등록하면, 스케줄러가 허용된 도구와 비용 한도 안에서 Claude Code CLI를 시작합니다. 상태 변화와 도구 호출, 비용, 결과는 한 실행 단위로 저장해 나중에 검토할 수 있습니다.

개인 PC에 설치하는 방식으로 설계했습니다. 기존 Claude Code 인증과 프로젝트 설정을 사용하며, 별도 DB 서버 없이 SQLite와 PM2로 실행합니다.

실행 모드

자동 실행

--permission-mode auto — 도구 사용을 자율 승인. 코드 읽기·쓰기·커밋까지 끝까지 수행 (Pro/Max 플랜 필요)

도구 제한

--allowedTools — 작업마다 필요한 Read·Edit·Bash·MCP 도구만 허용

프롬프트는 프리셋으로 관리한다. 자주 쓰는 지시문(코드 리뷰, 테스트 작성, 문서화 등)을 설정에 저장해두고, 작업 등록 시 드롭다운에서 선택해 채운 뒤 필요하면 그 자리에서 수정한다. 작업 타입을 코드에 박아두는 대신, 팀의 실제 사용 패턴이 프리셋으로 축적되도록 했다.


실제 화면

화면의 작업명, 실행 번호, 시간과 비용은 기능 흐름을 설명하기 위한 데모 데이터이며 운영 성과가 아닙니다.

태스크 등록 화면

AI Commons 테스트 자동화
/data/ai-commons
자동 실행
권한 무시
제한
테스트 자동 수정 ▾
전체 테스트를 실행하고, 실패하는 테스트가 있으면 원인을 분석한 뒤 수정해주세요. 수정 후 다시 테스트를 실행하여 통과를 확인하세요.
매일 01:00 KST
sonnet
$5.00

실행 로그 뷰어

실행 #42 완료 2m 34s
에이전트
전체 테스트를 실행하겠습니다. 먼저 프로젝트 구조를 확인합니다.
▸ Bashnpm test
▸ 결과종료 코드 1, 실패 3개
에이전트
3개의 테스트가 실패했습니다. script.service.spec.ts에서 타임아웃 관련 실패를 확인했습니다. 수정하겠습니다.
▸ Editsrc/script/script.service.spec.ts
결과: 성공
비용: $0.42 실행 시간: 154초

구조

SCHEDULED AGENT RUNTIME

예약 실행과 실시간 관측을 분리한 야간 작업 런타임

Scheduler가 실행을 트리거하고 Runner가 CLI 프로세스를 격리합니다. stream-json은 저장과 SSE 전송 경로로 분기됩니다.

예약 실행과 실시간 관측을 분리한 야간 작업 런타임 Scheduler가 실행을 트리거하고 Runner가 CLI 프로세스를 격리합니다. stream-json은 저장과 SSE 전송 경로로 분기됩니다. 01 WEB · ASTRO + REACT 02 CONTROL PLANE · NESTJS 03 EXECUTION & STATE TRIGGER SPAWN STREAM SSE Task dashboard schedule · status Live log viewer REST API task contract Scheduler cron trigger Runner process isolation Stream parser stream-json Log service SSE fan-out Claude Code CLI agent process MCP servers external tools SQLite WAL JSONL raw log
작업 상태는 SQLite WAL에, 원본 실행 로그는 JSONL에 남겨 조회성과 복구 가능성을 함께 가져갑니다.

Frontend는 Astro + React로 구성한 웹 dashboard입니다. task 등록·조회와 real-time log viewer를 제공합니다. Backend는 NestJS입니다. Scheduler가 cron trigger를 발생시키면 Runner가 Claude Code CLI를 child_process.spawn으로 실행합니다.

CLI 출력은 NDJSON stream으로 parsing해 SQLite와 JSONL 파일에 동시에 기록합니다. Frontend log viewer는 SSE로 연결되어 실행 중인 task log를 즉시 보여줍니다. Storage는 별도 DB 서버 없이 SQLite를 사용하며, WAL mode로 동시 읽기·쓰기를 처리합니다.


핵심 설계

Claude Code CLI를 프로그래밍으로 제어하기

Claude Code는 원래 대화형 CLI다. 이걸 비대화형으로 제어하려면 child_process.spawn으로 호출하면서 --output-format stream-json 플래그를 붙이면 된다. 그러면 구조화된 NDJSON 스트림이 stdout으로 나온다.

실행 모드는 세 가지 플래그로 스위칭된다 — --permission-mode auto와 --allowedTools를 작업 설정에 따라 조합한다. 모델(sonnet/opus/haiku), 비용 한도, MCP 설정도 모두 커맨드라인 인자로 전달한다. 코드 변경이 포함된 작업은 체크박스 하나로 git worktree 격리 실행을 켤 수 있어, 메인 브랜치를 건드리지 않고 밤 사이 실험할 수 있다.

CLI 실행을 프로세스로 감싸면서 권한 범위, 비용 한도, 작업 격리와 중단 조건을 애플리케이션 상태로 관리했다.

실시간 로그 파싱과 이중 기록

Claude Code의 stream-json 출력은 라인 단위 NDJSON이다. 스트림이 들어오는 즉시 라인별로 파싱해서 role을 기준으로 구조화한다. assistant, tool_use, tool_result, system — 각각 다른 형태로 저장된다.

기록은 두 곳에 동시에 한다. SQLite에는 쿼리 가능한 구조화 데이터로, JSONL 파일에는 원본 아카이브로. SQLite는 대시보드 검색과 필터링을 위한 것이고, JSONL은 나중에 재처리하거나 외부로 내보낼 수 있도록 남겨두는 것이다.

프론트엔드 로그 뷰어는 SSE로 연결된다. 실행 중에는 상태 메시지, 도구 호출과 결과를 확인할 수 있고, 종료 후에는 같은 기록을 검색하거나 다시 처리할 수 있다.

MCP로 외부 도구 연동

--mcp-config로 MCP 서버 설정을 넘기면 Claude Code가 외부 도구를 쓸 수 있다. Night Worker에서는 글로벌 MCP 설정을 정의해두고, allowedTools 패턴 매칭으로 태스크마다 사용 가능한 도구를 제한한다.

Atlassian 연동에서는 코드 리뷰 결과를 바탕으로 설정된 프로젝트에 Jira 이슈를 등록하는 작업을 구성했다. 외부 시스템을 변경하는 도구는 태스크별 허용 목록에 포함된 경우에만 사용할 수 있다.

이 구조로 저장소 안의 작업과 팀 도구의 후속 작업을 한 실행 기록에서 추적할 수 있다.

프리셋 기반 프롬프트 관리

초기 버전에는 review/implement/analyze 같은 태스크 타입을 코드에 박아두고 타입별 시스템 프롬프트를 주입했다. 그런데 실제로 팀이 쓰는 작업은 훨씬 다양했다 — 타입을 늘릴수록 경계가 애매해졌고, 정작 자주 쓰는 패턴은 프롬프트 안에 숨어 있었다.

그래서 타입을 걷어내고 프리셋으로 옮겼다. 자주 쓰는 지시문을 설정에서 관리하고, 작업 등록 시 드롭다운으로 불러와 채운 뒤 필요하면 그 자리에서 수정한다. "비대화형 실행이니 질문하지 말고 스스로 판단하라"는 기본 지시를 공통으로 넣고, 나머지는 팀이 프리셋으로 누적한다.

코드가 단순해졌고, 무엇보다 팀의 실제 사용 패턴이 프리셋 목록에 드러나기 시작했다. "이런 프롬프트가 잘 먹힌다"는 암묵지가 설정 화면에 축적되는 건 예상하지 못한 효과였다.

설치형 아키텍처 — 중앙 서버 없이

SaaS로 만들 수도 있었지만 설치형을 선택했다. 이유는 단순하다 — 팀이 이미 Claude Code를 쓰고 있고, API 키 관리나 별도 권한 설정 없이 그걸 그대로 활용하고 싶었다. 중앙 서버를 두면 보안 이슈도 생기고, 팀마다 Claude 플랜이 다르면 복잡해진다.

배포는 pm2 start 하나로 끝난다. Storage는 SQLite를 쓰기 때문에 DB 서버가 필요 없다. WAL 모드를 켜서 스케줄러가 쓰는 동안 대시보드가 읽을 수 있게 했다.

동시성 제어도 필요했다. 여러 태스크가 같은 프로젝트에서 동시에 실행되면 충돌이 생긴다. 프로젝트 단위 lock과 글로벌 concurrency 제한을 함께 두어 안전하게 처리한다.


확인한 변화와 운영 경계

팀 사용에서 확인한 변화는 예약 실행, 한곳에 모인 실행 이력, 반복 테스트 작업의 비동기화다. 생산성이나 야근 감소 효과는 아직 같은 조건으로 측정하지 않았으므로 성과 수치로 제시하지 않는다.

코드 변경 작업은 프로젝트별 잠금과 선택적 git worktree로 격리한다. 허용 도구와 비용 한도는 작업마다 지정하며, 생성된 변경은 사람이 검토한 뒤 병합한다.

신뢰할 수 있는 로컬 환경에서 사용하는 내부 도구이며, 운영 배포를 자율 실행하는 시스템으로 설계하지 않았다. 장기 실행의 체크포인트와 재개, 작업 품질 평가, 외부 변경의 멱등성은 다음 검증 과제다.