멈춘 CI 작업을 막는 건 claude -p뿐

-p로 Claude를 비대화형 실행: subprocess 연결, 허용 도구, 출력 형식, GitHub Actions 통합.

멈춘 CI 작업을 막는 건 claude -p뿐
Share

일반적인 claude 호출은 터미널을 사람이 지켜보고 있다고 가정합니다. CI 작업에서는 바로 그 가정 때문에 파이프라인이 멈춥니다.

-p가 TTY를 우회해 CI를 멈추지 않게 하는 방식

-p 플래그(--print의 별칭)는 Claude Code를 대화형 REPL에서 단일 배치 호출로 전환합니다. stdin을 읽고, 결과를 stdout에 쓴 다음 종료하는 방식입니다. 이 플래그 없이 GitHub Actions runner, Docker layer, cron job 같은 non-TTY 환경에서 실행하면 Error: stdin is not a TTY가 발생하고, 프로세스는 절대 오지 않는 입력을 기다리게 됩니다. 그래서 작업이 깔끔하게 실패하지 않고 멈춰 버립니다 . -p를 쓰면 명령은 다른 Unix 도구처럼 조합됩니다. 예를 들면 cat build-error.txt | claude -p 'explain the root cause' > output.txt처럼 사용할 수 있습니다 .

Quick Answer: claude -p는 Claude Code를 한 번 실행하고 끝나는 stdin-to-stdout 명령으로 바꾸며, 작업이 끝나면 종료됩니다. 이 플래그가 없으면 non-TTY 환경에서 Error: stdin is not a TTY가 발생하고 작업이 무기한 멈춥니다. 따라서 -p는 CI 실행을 종료 가능하게 만드는 핵심 플래그입니다.

최근에 이름이 바뀌었기 때문에 명칭도 짚고 넘어가겠습니다. 2025년 9월 29일, Claude Code SDK는 Claude Agent SDK로 이름이 변경되었고, 터미널을 구동하는 것과 동일한 에이전트 루프와 도구 세트를 노출합니다 . 더 이상 권장되지 않는 패키지인 @anthropic-ai/claude-codeclaude-code-sdk@anthropic-ai/claude-agent-sdk(npm v0.3.220, 주간 다운로드 약 760만 회 ) 및 claude-agent-sdk(Python v0.2.128 )로 대체되었습니다. 마이그레이션은 패키지를 교체하고 타입 이름 하나를 바꾸면 됩니다. ClaudeCodeOptionsClaudeAgentOptions가 됩니다. 기존 패키지는 여전히 설치되지만 더 이상 활발히 개발되지 않습니다 . 다만 -p 진입점은 이름 변경과 무관하게 그대로입니다.

스크립트형 runner를 준비할 때 -p 전에 갖춰야 할 것

Screenshot of https://code.claude.com/docs/en/agent-sdk/python

작업에서 claude -p를 호출하기 전에, 맞는 런타임과 비대화형 자격 증명을 준비해야 합니다. CLI 경로에는 Node.js 18+가 필요하며 npm install -g @anthropic-ai/claude-code로 설치합니다 . Python SDK 경로에는 Python 3.10+가 필요합니다 . TypeScript 또는 Python 라이브러리를 통해 루프를 직접 구동한다면 별도의 CLI 설치는 필요하지 않습니다. 두 SDK 패키지 모두 이제 네이티브 Claude Code 바이너리를 함께 제공합니다 .

환경에 ANTHROPIC_API_KEY를 설정한 뒤 --bare를 추가하세요. Anthropic은 스크립트 및 SDK 호출에 --bare 모드를 권장하며, 향후 릴리스에서는 -p의 기본값이 될 예정이라고 설명합니다 . 이 모드는 OAuth와 keychain 읽기를 건너뜁니다. 그래서 명시적인 API 키가 필요합니다. 또한 hooks, MCP servers, plugins, CLAUDE.md의 자동 탐지도 건너뛰기 때문에 모든 머신에서 호출이 동일하게 동작합니다. 이를 빼먹으면 재현성이 흔들립니다. runner가 어딘가에 남아 있던 CLAUDE.md를 조용히 불러온다면, 그것은 테스트했던 runner가 아닙니다.

설치 시에는 두 가지 안전장치를 두세요. sudo npm install -g는 사용하지 말고 , 특정 버전을 고정하세요. 예를 들어 @anthropic-ai/claude-agent-sdk@0.3.220처럼 지정하면 패치 릴리스가 스프린트 중간에 에이전트 동작을 조용히 바꾸는 일을 막을 수 있습니다.

쿼리는 파이프로 넘기고, 턴 수를 제한하고, -p stdout을 파싱하기

Screenshot of https://pypistats.org/packages/claude-agent-sdk

러너 준비가 끝나면 작업 루프는 유닉스 방식으로 조합 가능한 단일 호출이 됩니다. 컨텍스트를 파이프로 넣고, stdout에서 답을 읽고, 종료 코드를 확인하면 됩니다. claude -p는 stdin을 읽고 stdout에 쓰기 때문에 다른 도구처럼 체이닝할 수 있습니다. 예를 들어 cat build-error.txt | claude -p 'explain the root cause' --bare > output.txt처럼 사용할 수 있습니다 . stdout은 응답이고, 종료 코드는 성공 또는 실패를 알려 주므로 CI 단계에서 그 값에 따라 분기할 수 있습니다. 아래의 예시 Python 래퍼는 subprocess에서 같은 호출을 보여 줍니다. 여기서 실행한 코드는 아니므로, 검증된 실행 결과가 아니라 필요에 맞게 바꿔 쓸 형태로 보세요.

import shutil
import subprocess

prompt = "Say ok, then exit."
claude = shutil.which("claude")

print("plain `claude` can wait for interactive input in CI")
print("$ claude -p " + repr(prompt))

if not claude:
    raise SystemExit("claude CLI not found")

result = subprocess.run(
    [claude, "-p", prompt],
    text=True,
    capture_output=True,
    timeout=30,
    check=True,
)
print(result.stdout.strip())

기본 출력은 일반 텍스트입니다. --output-format json을 추가하면 session_id, 사용량, total_cost_usd 세부 내역을 담은 구조화 객체를 받을 수 있습니다. --output-format stream-json은 토큰 스트리밍용 newline-delimited 이벤트를 내보내며, --verbose --include-partial-messages와 함께 사용합니다 . 다음 단계에서 타입이 있는 payload가 필요하다면 --json-schema를 우선 사용하세요. JSON Schema에 맞는 출력을 강제하고 이를 structured_output 필드에 반환하므로, 자유 형식 텍스트를 정규식으로 처리하는 것보다 더 안정적입니다. 비용을 제한하려면 --max-turns N으로 반복 횟수를 묶고, 도구 범위는 --allowedTools로 좁힙니다.

allowedTools 값Claude가 할 수 있는 일
Read,Grep,Glob읽기 전용 분석, 쓰기나 셸 실행 없음
Bash,Read,Edit테스트를 실행하고 파일을 수정해 고침
Bash(git diff *)Glob 문법으로 Bash를 diff만 가능하게 제한

--dangerously-skip-permissions는 통제된 환경에서만 사용하세요. 모든 확인 프롬프트를 제거합니다 . 같은 구성은 GitHub Actions에서도 uses: anthropics/claude-code-action@v1로 실행할 수 있습니다(MIT 라이선스, 별 약 8,500개 ). 작업에는 prompt를 설정하고, --model, --max-turns, --allowedTools, --json-schema는 단일 claude_args passthrough로 넘기면 됩니다. v1 GA에서 이 하나의 입력이 베타 시기의 direct_prompt, mode, 그리고 플래그별 개별 입력을 대체했습니다 .

SIGTERM, 과다 지출, stdin 제한: 미리 봐야 할 것들

Pipe the query, cap the turns, parse -p stdout

claude -p가 무인으로 실행되기 시작하면 실패 양상은 "멈춰 있다"에서 "조용히 잘못된 일을 한다"로 바뀝니다. Claude Code v2.1.128 기준으로 파이프로 들어오는 stdin은 10 MB로 제한됩니다 . 그래서 큰 diff나 전체 파일 트리를 인라인으로 스트리밍하면 경고 없이 잘릴 수 있습니다. 큰 입력은 파이프에 욱여넣지 말고 파일 경로로 전달한 뒤 Claude가 도구 호출로 읽게 하세요.

종료 코드 처리도 그만큼 중요합니다. SIGTERM은 현재 턴을 중단하고, Bash 프로세스 트리를 종료하고, SessionEnd hook을 실행한 뒤 코드 143으로 종료합니다 . timeout 때 SIGTERM을 보내는 CI 시스템은 정확히 이 경로를 타므로, 143은 크래시가 아니라 timeout 신호로 다루세요. 백그라운드 Bash 작업은 최종 결과 이후 약 5초 뒤 종료되고, 백그라운드 subagent는 v2.1.182부터 10분 상한을 기다립니다. 이 값은 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS로 조정할 수 있습니다 . 긴 병렬 하위 작업은 이 상한을 올리지 않으면 조용히 사라질 수 있습니다.

설정 오류도 숨어 있을 수 있습니다. system/init 스트림 이벤트는 plugin_errorsmcp_server_errors를 노출합니다(v2.1.219에서 추가) . CI에서는 이 배열들이 비어 있지 않으면 실패하도록 게이트를 걸어 두세요. 그렇지 않으면 MCP 서버 설정이 잘못되어도 job은 깨지지 않은 채 agent 성능만 저하될 수 있습니다.

Anthropic은 GitHub Actions 안내에서 "모든 invocation은 API token과 runner minutes를 함께 소비한다"고 설명하며, --max-turns 제한, timeout 설정, concurrency control 사용을 권장합니다 (source: Claude Code docs).

마지막으로, 버전 변화가 빠릅니다. @anthropic-ai/claude-agent-sdk는 며칠 사이 0.3.200에서 0.3.220으로 이동했습니다 . action과 package 버전을 고정하고, 프로덕션에 적용하기 전에 canary job에서 업그레이드를 테스트하세요.

-p 확장하기: cron, --json-schema, 스크립트 기반 이어가기

단일 claude -p 호출이 동작하면, 세 가지 패턴으로 실제 파이프라인까지 확장할 수 있습니다. 서로 다른 프로세스 경계를 넘는 multi-turn 흐름에서는 --output-format json | jq -r '.session_id'로 세션을 캡처한 뒤, 이후 호출에서 그 ID를 --resume <session_id>에 넘기면 됩니다. 각 턴이 별도 프로세스여도 상태는 유지됩니다 . downstream 코드가 응답을 결정적으로 파싱해야 한다면 --json-schema를 전달하세요. Claude는 자유 텍스트 대신 structured_output 필드에 schema-conforming 출력을 반환합니다 .

무인 작업에서는 scheduled GitHub Action에 cron 트리거, 명시적인 prompt, 그리고 claude_args: "--model opus"를 조합해 일일 리포트를 실행할 수 있습니다. Bedrock 사용자는 use_bedrock: true를 설정하고 us.anthropic.claude-sonnet-4-6처럼 region prefix가 붙은 model ID로 바꿔 넣습니다 . 프로세스 내부에서는 명시적인 세션 제어나 interrupt가 필요할 때만 Python의 ClaudeSDKClient를 선택하세요. 실행만 시키고 결과를 받는 scripted call에는 query()가 맞는 기본 단위입니다 . 핵심은 이것입니다. claude -p를 조합 가능한 유닉스 도구처럼 다루고, 비용과 도구 범위를 제한하고, 버전을 고정하면 사람의 키보드 입력 없이도 어떤 runner에든 끼워 넣을 수 있습니다.

자주 묻는 질문

CI가 멈추지 않게 하는 -p의 역할은 무엇인가요?

-p 없이 실행하면 claude 명령은 stdin이 TTY인지 확인하고 대화형 입력을 기다립니다. Docker 컨테이너, GitHub Actions 러너, cron 작업처럼 TTY가 아닌 셸에서는 이 확인이 실패해 프로세스가 Error: stdin is not a TTY를 던지고 멈춥니다 . -p 또는 --print 플래그는 REPL을 완전히 건너뜁니다. stdin을 하나의 프롬프트로 읽고, 결과를 stdout에 쓴 뒤 종료하므로 터미널이 필요 없습니다 . 그래서 멈춰 있던 호출이 깔끔한 배치 호출로 바뀝니다.

CLI와 Agent SDK를 둘 다 설치해야 하나요, 아니면 하나만 있으면 되나요?

사용 방식에 따라 다릅니다. 두 SDK 패키지, 즉 @anthropic-ai/claude-agent-sdk(TypeScript)와 claude-agent-sdk(Python)는 이제 네이티브 Claude Code 바이너리를 함께 제공하므로, 프로세스 내부에서 SDK를 사용할 때는 CLI를 별도로 설치할 필요가 없습니다 . subprocess로 셸 호출을 하는 셸 스크립트라면 Node.js 18+가 필요한 npm install -g @anthropic-ai/claude-code로 CLI를 전역 설치하세요 . 둘 다 필요한 경우는 드뭅니다.

-p가 끝없이 실행되며 API 예산을 소진하지 않게 하려면 어떻게 해야 하나요?

--max-turns N으로 에이전트 반복 횟수를 제한하면 실행 시간과 토큰 지출을 함께 묶어둘 수 있습니다 . 여기에 --output-format json을 추가하면 모든 응답에 total_cost_usd 필드가 포함되어 실행 후 예산 확인이 가능합니다 . GitHub Actions에서는 무인 실행에 대해 Anthropic이 권장하듯, 작업 수준의 timeout-minutes를 강제 외부 한계로 설정하고 concurrency 제어도 사용하세요 .

claude-code-action이 @beta에서 @v1로 바뀌면서 무엇이 깨졌나요?

anthropics/claude-code-action의 v1 GA에서는 세 가지 breaking change가 도입되었습니다 . 첫째, mode 입력이 제거되고 자동 모드 감지로 대체되었습니다. 둘째, direct_promptprompt로 이름이 바뀌었습니다. 셋째, max_turns, model, custom_instructions, allowed_tools, mcp_config, override_prompt처럼 플래그별로 나뉘어 있던 입력들이 CLI로 직접 전달되는 하나의 claude_args 문자열로 합쳐졌습니다 . 마이그레이션할 때는 고정 참조를 @beta에서 @v1로 업데이트하세요.

스크립트에서 -p를 호출할 때 항상 --bare를 넘겨야 하나요?

네, 재현성을 위해 권장됩니다. --bare 플래그는 hooks, skills, plugins, MCP servers, auto memory, CLAUDE.md의 자동 검색을 건너뛰므로, 스크립트 호출이 모든 머신에서 동일하게 동작합니다 . 또한 OAuth와 keychain 읽기를 건너뛰고 ANTHROPIC_API_KEY 또는 apiKeyHelper를 통한 인증을 강제하는데, CI에서는 이 방식이 적합합니다 . Anthropic은 이를 스크립트 및 SDK 호출의 권장 모드라고 부르며, 향후 릴리스에서 -p의 기본값이 될 예정이라고 밝힌 바 있습니다 .

이 글이 도움이 되셨다면, 새 글이 올라올 때마다 이메일로 받아보세요.

구독하기