내장 cron으로 launchd 32개 잡을 정리 판단 — 자동화 기반 재점검
요약
macOS의 launchd에 분산된 자동화 잡(Job)들을 Claude Code의 내장 cron으로 재점검하고 이전하는 과정을 다룬 글입니다. 이 과정은 단순히 서버 비용 절감보다는, 복잡한 설정 구조를 단순화하여 '무엇을 시키고 싶은지'라는 핵심 로직만 남기는 데 큰 효과가 있습니다. 이전 작업은 공통 헬퍼 스크립트와 plist 파일의 복잡성을 제거하고, 실행할 기능 자체에 집중하게 만듭니다.
핵심 포인트
- launchd 잡 관리의 어려움: 개수 불일치 및 비활성화된 잡 누락 위험.
- Claude Code 내장 cron으로 이전 시 설정 구조가 크게 단순화됨.
- 복잡한 래퍼 스크립트와 plist 대신 핵심 로직만 남기는 것이 중요함.
장부에 17개라고 적힌 잡이 실제로는 33개였다
우리 회사는 거의 모든 정기 업무를 macOS의 launchd에 올리고 있다. 아티클 공개, 매출 취득, 부서 에이전트 구동, Slack 알림 등. CEO는 나 혼자고, 실무는 전부 스크립트가 돌린다.
2026년 9월 25일에 재점검을 하다가 개수가 맞지 않는 것을 알게 되었다. 경영 상태를 기록하는 STATE.md에는 계속 'launchd 잡: 17개'라고 쓰여 있었는데, ~/Library/LaunchAgents에 놓인 com.joinclass.*.plist는 33개였다. 내가 무엇을 구동하고 있는지 16개 분량은 파악하지 못했다.
게다가 단순한 개수 문제가 아니었다. 재점검에서 나온 사고가 세 가지 있다.
- Qiita 공개 잡이
Disabled: True상태로 5개월 동안 멈춰 있었다 -
-..claude/skills/에 놓은 13개의 스킬이 6개월 동안 한 번도 로드되지 않았다 (단일.md가 아니라<이름>/SKILL.md가 올바른 형식이었다) - - 역할을 마친 일회성 잡이, 완료 후에도 매일 계속 구동되고 있었다
공통점은 '설정했다고 생각했지만 움직이지 않는' 형태의 실패다. 에러가 발생하는 것이 아니다. 아무 일도 일어나지 않을 뿐이라 아무도 알아차리지 못한다.
그 반성 위에서, 지금 나는 Claude Code의 내장 cron(스케줄 실행)으로 잡을 옮기는 작업을 진행하고 있다. 이 아티클은 그 옮길 것/안 옮길 것의 경계에 대한 이야기다.
얼마나 효과적인가: 줄일 수 있는 것은 돈이 아니라 '확인하는 시간'
먼저 결론부터 쓴다. launchd에서 내장 cron으로 옮겨도 서버 비용은 1엔도 줄지 않는다. 둘 다 Mac 위에서 작동하기 때문이다.
효과가 있는 곳은 다른 부분, 즉 잡 하나당 필요했던 '래퍼 스크립트 + plist + 로그 경로'의 3세트가 1개로 줄어드는 것이다. 우리 auto-*.sh는 평균적으로 100~200줄이 있고, 그 내용 대부분은 비즈니스 로직이 아니다. 공통 헬퍼 common.sh를 읽고, PATH를 통해, claude -p를 실행하고, 결과를 Slack에 던지고, git에 커밋한다. 이 정형 부분이 3
이전환 대상의 전형적인 예는, 기사 재고 보충이나 매출 요약 같은 'AI가 판단하게 하고 그 결과를 어딘가에 기록하는' 종류의 작업이다.
launchd 시절에는 공통 헬퍼를 이렇게 끼워 넣었다.
#!/bin/bash
source "$(dirname "$0") /common.sh"
check_cost_limit || exit 1
...
--fallback-model
그리고 --max-budget-usd
은 무인 실행을 위해 claude -p를 호출하는 9개의 스크립트 12곳 모두에 넣었다. 주 모델이 혼잡하면 작업 전체가 중단되었기 때문에, 중단되는 대신 Sonnet으로 완료되게 했다. 예산 상한선은 실측 1회당 $0.02 대비 $3로, 150배의 여유가 있다. 평소에는 발동하지 않고, 루프 폭주할 때만 작동한다는 방식으로 배치했다.
내장 cron으로 옮기자 이 외부 구조 전체가 불필요해진다. 남는 것은 '무엇을 시키고 싶은지'라는 한 문장뿐이며, 로그 경로 명시도 PATH 명시도 필요 없다. 우리가 이전의 첫 번째 그룹으로 선택한 것이 바로 이런 구조를 가진 작업군이었다.
실제로 빠진 함정 3가지
그 1: Disabled: True는 조용히 작동한다. plist에 작성되어 있어도
launchctl load
할 때 간혹 무시될 뿐, 오류는 발생하지 않는다. 이전하기 전에 반드시 목록을 가져와서 유효한 것과 비활성화된 것을 나누어 센다.
for p in ~/Library/LaunchAgents/com.joinclass.*.plist; do
printf "%s%-40s %s\n" "$(basename "$p")"
"$(plutil -extract Disabled raw "$p" 2>/dev/null || echo enabled)"
...
그 2: 로그의 위치가 작업 이름과 일치하지 않는다. 우리 note 공개 작업은 표준 출력을 별도의 파일로 리다이렉트하고, plist의 StandardOutPath는 비어 있었다. 이전할 때 '로그가 없어서 죽었다'고 오판할 뻔했다. 에일리어스(alias) 표를 갖게 되면서 위기를 넘겼다.
그 3: 역할을 마친 작업이 남아있다. 특정 서적을 공개하기 위한 일회성 작업이, 대상 서적이 이미 공개된 후에도 매일 기동하고 있었다. 일회성 작업에는 처음부터 종료 조건을 작성해 두는 것이 정답이며, 이것이 불가능하다면 내장 cron처럼 원샷 실행을 순순히 다룰 수 있는 시스템으로 옮기는 것이 좋다.
검증: 이전이 성공했는지 판단하기
옮긴 직후에는 기존 작업을 삭제하지 않고 Disabled로 설정한 후, 두 쪽의 로그를 1주일간 나란히 놓는다. 이것만으로 충분하다.
내장 cron 측만 업데이트되었고, 출력 내용이 기존 작업과 동일하면 이전 성공이다. 기존 작업을 .disabled로 리네임하여, JOBS.md 표에서 지운다. 우리는 중단된 작업도 테이블에 취소선으로 남기고, 언제 누가 판단해서 멈췄는지 적어둔다. 수가 맞지 않게 되는 원인 대부분은 '삭제한 것을 기록하지 않았기' 때문이다.
무엇을 맡기고, 무엇을 맡기지 않을 것인가
마지막으로 경영 판단 이야기로 돌아간다. 33개의 작업을 내장 cron에 전부 모으려는 생각은 없다. 선을 긋는 방식은 이렇다.
맡길 것: 글 생성, 요약, 재고 보충 판단, 일일 리포트. 실패해도 다음날 재시도하여 만회할 수 있는 것 -
맡기지 않을 것: 모니터링 자체, 매출 데이터 획득, 외부 공개 실행. 멈췄다는 사실을 인지하는 메커니즘만은, 멈추는 쪽과 같은 기반에 두지 않는다
자동화 정리정돈에서 가장 효과적이었던 것은, 작업을 줄였거나 옮긴 것이 아니라, '작동하고 있다고 착각하는 것'을 감지하는 메커니즘을 하나 갖게 된 것이었다. 워치독(Watchdog)을 넣은 첫날, 5개월 동안 멈춰 있던 작업이 발견되었다. 그동안 5개월 동안 나는 해당 매체에 기사를 내고 있는 줄 알고 있었다.
자동화의 진짜 비용은, 작동시키는 것이 아니라, 작동하고 있다고 오해하며 계속하는 기간에 있다.
この記事のテーマは、自社の自動化基盤を題材にした技術書シリーズから抜き出したものです。launchdからClaude Code運用までの構成、失敗事例、実際のスクリプトは書籍側で通しで扱っています。
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기