6.7 KiB
title, type, namespace, tags, created, updated, confidence
| title | type | namespace | tags | created | updated | confidence | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| openclaw-claude-proxy (Node.js) — внутреннее устройство и проблемы | reference | personal |
|
2026-06-15 | 2026-06-15 | 0.9 |
openclaw-claude-proxy (Node.js) — внутреннее устройство и проблемы
Обзор
npm пакет: openclaw-claude-proxy v1.0.8+
Порт: 3456
Язык: TypeScript (компилируется в JS)
Репозиторий: mehdic/openclaw-claude-proxy
Форк: mnemon-dev/claude-max-api-proxy + OpenClaw compatibility PR
Текущий статус: fallback (активен Python wrapper на порту 8090)
Архитектура
Hermes → POST /v1/chat/completions (порт 3456, Node.js Express)
└── proxy преобразует OpenAI-формат → stream-json протокол
└── спавнит claude CLI как child process
└── claude --output-format stream-json --verbose
└── читает/пишет JSON через stdin/stdout pipe
Runtime-модели (CLAUDE_PROXY_RUNTIME)
stream-json (default):
- init pool: несколько долгоживущих
claudesubprocess'ов - session pool: переиспользование сессий между запросами
- промпт-кэш между вызовами сохраняется
- использует
--output-format stream-json --input-format stream-json
print (fallback):
- на каждый запрос spawn'ит свежий
claude --print - медленнее, но изолированно
- не требует совместимости stream-json протокола
Контейнерный сетап
~/Docker/claude-proxy-node/Dockerfile — образ на node:20-alpine:
FROM node:20-alpine
RUN npm install -g openclaw-claude-proxy @anthropic-ai/claude-code
EXPOSE 3456
CMD ["claude-proxy", "3456"]
Образ не собран — в работе.
Проблемы
1. Orphaned claude subprocess'ы
Механизм: stream-json runtime держит пул долгоживущих claude child process'ов через child_process.spawn(). Proxy управляет их жизненным циклом: при нормальном shutdown (SIGTERM) должен завершить pool. Но:
- SIGKILL родительского proxy (kill -9, docker stop --time=0, launchd force quit) — Node.js не успевает выполнить cleanup
- SIGTERM с таймаутом — если graceful shutdown превышает лимит времени, Node.js процесс убивается до завершения pool
- launchd KeepAlive — при падении proxy по любой причине launchd убивает и перезапускает, но старые
claudeпроцессы с PPID 1 остаются в системе - docker restart — если контейнер перезапускается без
--time(grace period), старые процессы переживают контейнер
На macOS orphan'ы выглядят как claude с PPID=1. Их количество растёт со временем.
2. Нет живого вывода claude CLI
Причина: proxy читает stdout claude исключительно как JSON pipe. Человеческий вывод markdown/thinking/прогресса claude пишет в stderr, но proxy его не форвардит.
По умолчанию stderr claude либо:
- не пипруется вообще (pipe не открывается) — stderr уходит в /dev/null контейнера
- пипруется и выбрасывается
В режиме print проблема та же — proxy ждёт завершения claude --print и возвращает результат целиком, не транслируя промежуточный вывод.
3. claude spawn'ит свой bash без контекста Hermes
Механизм: proxy запускает claude через child_process.spawn('claude', args, {env: process.env}). Ниже по цепочке:
launchd → claude-proxy-start.sh → claude-proxy (node) → claude CLI (subprocess)
└── bash -c "..."
Claude Code CLI для выполнения инструментов (Bash, Write, Edit) внутри себя спавнит bash -c "...". Этот bash:
- наследует env от
claudeпроцесса - НЕ имеет
HERMES_HOME, кастомногоPATH,SHELL=/bin/zshс профилем Hermes - использует системный
/bin/bashили/bin/sh
В контейнере (alpine) bash вообще может отсутствовать — claude CLI использует /bin/sh.
Предложенные подходы
Против orphaned процессов
- Docker
--initфлаг: запускать контейнер сinit: trueв docker-compose (илиdocker run --init). Используетtiniкак PID 1 — корректно форвардит SIGTERM и перезахоранивает orphan'ов. - SIGTERM handler в Node.js: добавить
process.on('SIGTERM', ...)который форсированно убивает весь child process pool перед exit. - Health check + restart policy: не
always, аunless-stoppedс--time=30grace period.
Против отсутствия вывода CLI
- Проброс stderr: pipe'ить stderr
claudeв stderr proxy — видно вdocker logsили launchd логах. - Режим
print: переключитьCLAUDE_PROXY_RUNTIME=print— теряется производительность stream-json, но логов больше. - Node.js
stream-jsonмониторинг: логировать каждую N-ную JSON строку из stdout claude для отладки.
Против изоляции shell
- В Docker: установить bash в контейнер (
apk add bash), установитьSHELL=/bin/bashв env. - Передача
HERMES_HOME: смонтировать~/.hermesв контейнер и передатьHERMES_HOMEв env claude CLI. - wrapper-скрипт для claude: заменить бинарник
claudeв контейнере на shell wrapper, который ставит профиль Hermes перед вызовом реальногоclaude.