
API 과금 없이 Claude를 쓰고 싶었다
OpenClaw에서 Claude를 쓰려면 원래 Anthropic API 키가 필요하다. 종량제 과금이라 크론잡이나 블로그 자동화처럼 하루에 수십 번 호출하는 용도로는 부담이 꽤 크다.
그런데 Claude Code를 Pro나 Max로 구독하고 있으면, 그 CLI를 OpenClaw 백엔드로 물려서 API 비용 0원에 Claude를 굴릴 수 있다. OpenClaw에 claude-cli라는 빌트인 백엔드가 이미 들어있거든.
설정 자체는 어렵지 않은데, 한 가지 함정이 있었다. 텔레그램에서 응답이 실시간으로 안 오고 한 번에 뭉텅이로 오는 문제. 이걸 해결하려면 dist 파일 패치가 필요하다.
오늘은 빌트인 claude-cli 백엔드 설정부터 텔레그램 스트리밍 패치까지, 실제로 적용한 과정을 정리해봤다.

빌트인 claude-cli 백엔드란
OpenClaw 2026.4.x 버전부터 claude-cli라는 CLI 백엔드가 내장되어 있다. 예전에는 커스텀 wrapper 스크립트를 만들어서 Claude Code CLI를 감싸야 했는데, 이제 그럴 필요가 없다.
빌트인이 자동으로 처리해주는 것들이 꽤 많다.
--output-format stream-json— 스트리밍 출력--permission-mode bypassPermissions— 자동화용 권한 우회bundleMcp: true— MCP 서버 자동 번들링watchdog— 프로세스 hang 감지 (idle 120초, total 600초)clearEnv: true— 환경변수 격리serialize: true— 요청 순차 처리
직접 설정해야 하는 건 CLI 경로, 모델 매핑, 세션 모드 정도다.

설정 방법: 3가지만 하면 된다
전제 조건은 두 가지. Claude Code CLI가 설치되어 있어야 하고, claude login으로 구독 인증이 완료되어야 한다.
~/.openclaw/openclaw.json의 agents.defaults 안에 세 가지를 추가한다.
1. cliBackends 등록
"cliBackends": {
"claude-cli": {
"command": "/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js",
"modelArg": "--model",
"modelAliases": {
"opus": "claude-opus-4-6",
"sonnet": "claude-sonnet-4-6",
"haiku": "claude-haiku-4-5"
},
"sessionMode": "always"
}
}
command에는 Claude Code CLI의 절대 경로를 넣는다. macOS Homebrew 설치 기준이고, npm global이나 Linux라면 경로가 다르다. which claude로 확인하면 된다.
sessionMode: "always"는 매 대화에 세션 ID를 부여해서 맥락을 유지시켜준다. 크론잡이나 에이전트처럼 이어지는 대화가 필요한 경우 필수.
2. 모델 등록
"models": {
"claude-cli/sonnet": { "alias": "cli-sonnet" },
"claude-cli/haiku": { "alias": "cli-haiku" },
"claude-cli/opus": { "alias": "cli-opus" }
}
3. 기본 모델 지정
"model": { "primary": "claude-cli/sonnet" }
이러면 에이전트, 크론잡, 블로그 파이프라인 전부 claude-cli/sonnet을 기본으로 쓰게 된다. 경량 작업에는 claude-cli/haiku, 고품질이 필요하면 claude-cli/opus를 지정하면 되고.

여기서 끝이 아니었다 — 텔레그램 스트리밍 문제
설정을 마치고 openclaw chat으로 테스트하면 잘 된다. 근데 텔레그램 봇에서 메시지를 보내보면 문제가 보인다.
응답이 실시간으로 타이핑되듯 오는 게 아니라, 전체 응답이 완성된 뒤에 한 번에 뭉텅이로 온다. 이건 사용 경험이 확 나빠지는 부분이다.
원인을 파봤더니, OpenClaw의 agent-runner.runtime 코드에서 CLI 백엔드 경로와 embedded(API) 경로의 처리가 달랐다.
- embedded 경로:
onPartialReply콜백이 정상 연결됨 → 텔레그램에 실시간 전달 - CLI 경로:
runCliAgent호출 시params.opts.onPartialReply를 완전히 드랍
CLI가 내부적으로 emitAgentEvent({stream:"assistant"})로 이벤트 버스에 delta를 올리긴 하는데, 그걸 텔레그램의 onPartialReply 콜백으로 프록시하는 코드가 CLI 경로에는 없는 거다.
패치: 3곳만 고치면 된다
대상 파일은 OpenClaw dist 폴더의 agent-runner.runtime-*.js. 해시값이 버전마다 다르니까 패턴으로 찾는다.
find $(npm root -g)/openclaw/dist -name "agent-runner.runtime-*.js"
패치 전에 반드시 백업.
cp agent-runner.runtime-XXXXXXXX.js agent-runner.runtime-XXXXXXXX.js.bak
패치 1: import에 onAgentEvent 추가
파일 상단의 agent-events import 라인을 찾는다.
// 변경 전
import { i as emitAgentEvent, u as registerAgentRunContext } from "./agent-events-XXXXXXXX.js";
// 변경 후 — onAgentEvent 추가
import { i as emitAgentEvent, l as onAgentEvent, u as registerAgentRunContext } from "./agent-events-XXXXXXXX.js";
onAgentEvent의 export alias(l)는 버전마다 다를 수 있다. agent-events-*.js 파일에서 실제 export를 확인해야 한다.
패치 2: CLI 경로에 스트리밍 브릿지 삽입
runCliAgent 호출 직전, let lifecycleTerminalEmitted = false; 바로 아래에 추가한다.
const _unsubCliStream = params.opts?.onPartialReply ? onAgentEvent((evt) => {
if (evt.runId === runId && evt.stream === "assistant" && evt.data?.text) {
try { params.opts.onPartialReply({ text: evt.data.text }); } catch {}
}
}) : null;
이 코드가 하는 일은 간단하다. CLI가 이벤트 버스에 올리는 assistant 스트림 이벤트를 구독해서, 텔레그램의 onPartialReply 콜백으로 전달하는 브릿지 역할이다.
패치 3: finally에서 구독 해제
같은 함수의 finally 블록 첫 줄에 한 줄 추가.
_unsubCliStream?.();
에러든 정상 종료든, 이벤트 구독을 반드시 해제해서 리소스 누수를 방지한다.
패치 검증과 재시작
패치 후 문법 검사부터.
node --check agent-runner.runtime-*.js && echo "syntax ok"
패치가 제대로 들어갔는지 확인.
grep -n "onAgentEvent\|_unsubCliStream" agent-runner.runtime-*.js
3곳이 나와야 한다. import 라인, 브릿지 생성, 구독 해제. 하나라도 빠지면 안 된다.
확인됐으면 Gateway를 재시작한다.
pm2 restart openclaw-gateway
텔레그램에서 메시지를 보내보면, 응답이 타이핑되듯 실시간으로 들어오는 걸 확인할 수 있다.
주의할 점 하나
OpenClaw을 업데이트하면 dist 파일이 덮어씌워진다. 즉 이 패치가 날아간다.
업데이트 후에는 항상 이걸 체크해야 한다.
grep "onAgentEvent" agent-runner.runtime-*.js
결과가 안 나오면 재패치. 백업 파일이 있으니 diff로 비교하면 빠르다.
솔직히 이 부분이 좀 아쉽긴 하다. OpenClaw 쪽에서 CLI 경로에도 onPartialReply를 제대로 연결해주면 패치가 필요 없을 텐데. 이슈로 올려볼까 싶기도 하고.
정리하면
OpenClaw 빌트인 claude-cli 백엔드를 쓰면 API 비용 없이 Claude를 에이전트, 크론잡, 블로그 자동화에 활용할 수 있다. 설정은 openclaw.json에 cliBackends + 모델 등록 + 기본 모델 지정 3가지.
다만 텔레그램 실시간 스트리밍이 필요하면 agent-runner.runtime 패치가 필수다. 이벤트 버스의 assistant 스트림을 onPartialReply로 프록시하는 브릿지 코드 3줄이 핵심.
예전에 커스텀 wrapper를 만들어서 쓰다가 빌트인으로 갈아탔는데, 유지보수 부담이 확 줄었다. wrapper 관리, 타임아웃 맞추기, stderr 에러 핸들링 같은 삽질이 사라진 게 제일 크다.
📌 함께 보면 좋은 글
'AI.IT' 카테고리의 다른 글
| AI 코딩 도구 쓰면 19% 느려진다는 연구, 실제 어떻게 된 건가 (0) | 2026.04.17 |
|---|---|
| Ollama 0.19 MLX 전환 후기, 맥북 로컬 AI가 이렇게 달라졌다 (1) | 2026.04.16 |
| MCP 서버 1만 개 중에 결국 남은 건 3개였다 (1) | 2026.04.15 |
| 바이브 코딩 툴 비교: Cursor vs Bolt.new vs Lovable, 셋 다 써봤다 (1) | 2026.04.14 |
| Gemini Flash Lite 잘 쓰는 사람들의 공통점, 실무 활용 핵심 (0) | 2026.04.14 |