claude -p에서 인터랙티브 스킬이 멈추는 이유 — Phase 2 승인 트랩과 3가지 우회법

claude -p에서 인터랙티브 스킬이 멈추는 이유 — Phase 2 승인 트랩과 3가지 우회법

cron에서 스킬을 돌렸더니 Phase 2에서 멈췄다

claude -p를 cron에 넣고 blog-draft 스킬을 자동화했다. 첫 실행 로그를 열었더니 스킬이 프레임 후보 3개를 출력한 뒤 프로세스가 종료돼 있었다. 응답을 기다리는 주체는 아무도 없었다. 이게 Phase 2 승인 트랩이다.

이 글은 트랩의 원인을 분해하고, 자동화 파이프라인에서 인터랙티브 스킬을 끝까지 실행시키는 우회법 3가지를 다룬다. cron·CI 환경에서 claude -p로 스킬을 돌리고 있다면 바로 적용 가능한 내용이다.

같은 상황을 겪어봤다면

CI 잡이나 cron에서 claude -p로 스킬을 호출했는데 절반만 실행되고 종료된 것을 본 적 있다. exit code는 0이라 성공처럼 보이지만 결과물은 없다. stdout 마지막 줄이 "진행 시 Phase 3 시작"이라는 텍스트였는데, 그걸 읽고 Enter를 칠 사람은 없었다. 스크립트는 정상 종료됐고 아무 일도 일어나지 않았다.

이게 반복되면 자동화 자체를 의심하게 된다. 문제는 claude -p가 아니라 스킬 설계의 가정에 있다.

원리와 증상: 단일 턴 vs 멀티턴 가정의 충돌

claude -p는 비대화형 모드다. 프롬프트 하나를 받아 응답 하나를 돌려주고 종료한다. 대화 루프가 없다. 두 번째 입력을 기다리는 구조가 아니다.

반면 대부분의 인터랙티브 스킬은 멀티턴 가정으로 설계돼 있다. 전형적인 3-Phase 패턴을 보면:

  • Phase 1 — 재료 수집, 분석 결과 출력
  • Phase 2 — 방향 제안, 사용자 승인 대기 ← 여기가 트랩
  • Phase 3 — 승인 후 본문 실행

Phase 2는 설계상 응답을 기다리는 체크포인트다. 대화형 환경에서는 "1번으로"라고 답하면 Phase 3가 시작된다. claude -p 환경에서는 Phase 2 출력 직후 Claude가 종료한다. 다음 턴이 오지 않기 때문이다.

스킬이 버그가 있는 게 아니다. 스킬은 계획대로 동작했다. 스킬이 가정한 환경과 실제 실행 환경이 달랐을 뿐이다. 이 구조적 불일치가 Phase 2 승인 트랩이다.

실제로 재현하면 이렇게 된다:

# cron 또는 CI에서 실행
claude -p "Use blog-draft skill for topic: claude -p 자동화 패턴"

stdout 끝부분:

📐 프레임 + 제목 제안

프레임: surface-to-deep (자동 판정)
근거:   "방법/how-to" 신호 검출

제목 후보:
  1. (추천) claude -p 자동화 — 원리 3가지
  2. claude -p 써보고 생긴 의문 N가지
  3. 비대화형에서 인터랙티브까지

진행 시 Phase 3 시작. 다른 프레임 원하면 재호출.

exit code: 0. 결과물: 없음. Phase 2 승인 트랩의 전형적인 출력이다.

Phase 2 승인 트랩을 피하는 3가지 방법

방법 1: 프롬프트에 결정을 미리 포함한다

가장 단순하다. Phase 2에서 Claude가 선택하게 될 내용을 프롬프트에 미리 명시하면, 스킬이 Phase 2 제안을 출력한 뒤 컨텍스트에 이미 있는 결정에 따라 Phase 3를 진행할 수 있다.

claude -p "Use blog-draft skill for topic: claude -p 자동화 패턴. \
  Frame: surface-to-deep. \
  Title: 'claude -p 자동화 — 원리 3가지'. \
  Slug: claude-p-automation-patterns. \
  Proceed through all phases without stopping for approval. \
  Output dir: /tmp/blog-drafts/"

스킬이 Phase 2에서 제안을 출력하더라도, "진행 시 Phase 3 시작"이라는 자체 텍스트 조건을 컨텍스트의 선결정과 함께 해석해 Phase 3를 실행한다. 대부분의 스킬에서 통한다.

단점: 스킬이 AskUserQuestion 도구를 명시적으로 호출하거나, 다음 입력을 물리적으로 기다리는 구조라면 통하지 않는다. 그런 경우엔 방법 2나 3가 필요하다.

방법 2: 시스템 레벨에서 자율 진행 지시를 주입한다

--system 옵션으로 비대화형 배치 모드 지시를 전역 주입한다. 스킬 코드를 수정하지 않아도 된다.

claude -p \
  --system "You are running in non-interactive batch mode. \
    Skip all approval checkpoints automatically. \
    Make the best decision for each phase and proceed without pausing for user input. \
    If a skill asks to choose between options, pick the recommended option (marked with 추천) and continue." \
  "Use blog-draft skill for topic: claude -p 자동화 패턴. Output dir: /tmp/blog-drafts/"

cron 래퍼 스크립트에 이 system prompt를 고정해두면 모든 스킬 호출에 일괄 적용된다. 스킬마다 프롬프트를 바꾸지 않아도 된다.

주의: 의도치 않은 승인 단계도 함께 건너뛴다. 파일 대량 수정이나 외부 API 호출이 포함된 스킬에선 이 지시가 예상치 못한 실행을 유발할 수 있다. cron 전용 CLAUDE.md를 별도로 관리하는 것이 현실적이다.

방법 3: Phase를 분리해서 직렬 호출한다

가장 확실하다. Phase 1~2를 한 번의 claude -p로 실행하고 결정을 JSON으로 저장한 뒤, Phase 3~6을 다른 claude -p로 이어받는다.

#!/bin/bash

# 1단계: 재료 수집 + 프레임 결정 → JSON 저장
claude -p \
  --output-format stream-json \
  "Run blog-draft Phase 1-2 only for topic: claude -p 자동화 패턴. \
   Output a single JSON object: {\"frame\": ..., \"title\": ..., \"slug\": ...}. \
   Do not proceed to Phase 3." \
  | python3 -c "
import sys, json
for line in sys.stdin:
    try:
        obj = json.loads(line)
        if obj.get('type') == 'content_block_stop':
            break
        if obj.get('type') == 'content_block_delta':
            print(obj['delta'].get('text', ''), end='')
    except:
        pass
" > /tmp/phase2_decision.json

# 2단계: 결정을 컨텍스트로 넣어 Phase 3~6 실행
DECISION=$(cat /tmp/phase2_decision.json)
claude -p "Run blog-draft Phase 3-6 using this decision: $DECISION. \
  Output dir: /tmp/blog-drafts/"

--output-format stream-json은 응답을 JSON 이벤트 스트림으로 내보낸다. 중간 결정을 파일로 직렬화하면 두 프로세스 간 상태를 안정적으로 전달할 수 있다. 실패하면 1단계부터 재시도하면 되고, Phase 2 결정만 재사용해서 3단계를 다시 돌릴 수도 있다. 방법 1, 2보다 구현 비용이 있지만 파이프라인의 각 단계를 독립적으로 관찰하고 재시도할 수 있다는 점에서 프로덕션 자동화에 더 적합하다.

스킬 설계 기준: 자동화 맥락을 처음부터 결정하라

세 방법의 공통 전제는 같다. 스킬 설계자가 "이 스킬은 자동화 파이프라인에서도 쓰일 것인가?"를 초기에 결정하지 않았다는 것이다. 이 결정을 뒤늦게 하면 사용자 쪽에서 우회법을 조립해야 한다.

스킬을 직접 만든다면 설계 단계에서 다음 세 가지를 결정하는 것이 나중보다 훨씬 싸다.

승인 체크포인트를 선택적으로 스킵 가능하게 만든다. 컨텍스트에 batch_mode: true 플래그나 "proceed without approval" 같은 지시가 있을 때 Phase 2를 Claude 자율 결정으로 넘어가도록 프롬프트 조건을 명시한다.

중간 상태를 직렬화 가능하게 설계한다. Phase 2의 결정이 JSON 포맷으로 뽑힐 수 있으면 Phase 간 프로세스를 분리할 수 있다. 분리 가능하다는 건 재시도도 가능하다는 의미다.

Phase 수를 최소화한다. 승인 체크포인트 하나당 자동화 복잡도가 올라간다. "사용자가 반드시 선택해야 하는가?" 아니면 "Claude가 최선 선택을 자율 결정할 수 있는가?"를 체크포인트마다 물어라. 후자라면 Phase를 줄여라.

반론도 있다. 승인 체크포인트를 없애면 스킬이 잘못된 방향으로 끝까지 달린다. 비용이 높은 작업—대량 API 호출, 파일 대량 수정—에서 체크포인트는 실질적인 안전망이다. 이 경우 해법은 체크포인트를 없애는 게 아니라 사전 입력으로 대체하는 것이다. 실행 전 결정을 확정하고, 실행 중에는 멈추지 않는 구조다. 방법 1이 그 구조의 단순한 버전이다.

한 줄 정리

Phase 2 승인 트랩의 원인은 설계 불일치다. 스킬은 멀티턴을 가정하고, claude -p는 단일 턴이다. 지금 트랩에 걸려 있다면 방법 1(프롬프트 선결정)부터 시도해봐라. 프롬프트 한 줄 추가가 대부분의 경우 충분하다. 스킬을 직접 만든다면 자동화 맥락을 설계 단계에서 결정하는 것이 나중에 우회법을 조립하는 것보다 훨씬 낫다.

관련 글

태그: #claude-code #비대화형 #자동화 #claude스킬 #cron #cli #배치실행

댓글

이 블로그의 인기 게시물

Claude Opus 4.7 출시 총정리 — 뭐가 달라졌고 지금 써야 하나

Claude Code로 블로그 발행 15분을 1줄로 — 해고 후 첫 자동화 경험

Claude Code 설치 3단계 — macOS·Windows·Linux 공통