12 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 CLI сам исполняет инструменты вместо Hermes
Проблема: Claude Code CLI сам выполняет Bash/Read/Write/Edit инструменты внутри своей сессии, а не возвращает tool_use блоки Hermes'у для исполнения.
Текущий flow:
Hermes → claude-proxy (Node.js) → claude CLI
└── сам делает Bash, Read, Write
└── bash -c "..."
Hermes видит только финальный текст ответа. Он теряет контроль над:
- какие файлы читаются
- какие команды выполняются
- кто их логирует и аудитит
- allower/denier политики Hermes игнорируются
Правильный flow (caller-dispatched tools):
Hermes → claude-proxy (Node.js) → claude CLI (только генерация)
↓ текст ответа с tool_use блоками
Hermes ── парсит tool_use ── выполняет через свои инструменты ── результат → обратно в claude CLI
Для этого proxy должен:
- Выключить инструменты в
claudeCLI — передатьdisallowed_tools = ["Bash", "Read", "Write", "Edit", "Glob", "Grep"] - Остановиться после первого assistant-сообщения
- Отдать tool_use блоки Hermes'у как OpenAI
tool_calls - Принять результат выполнения от Hermes и передать обратно в
claudeдля продолжения (multi-turn)
Текущий openclaw-claude-proxy не поддерживает этот flow корректно: stream-json протокол для multi-turn interaction сломан начиная с claude CLI 2.1.141. В режиме print multi-turn вообще невозможен.
Python wrapper (claude-code-openai-wrapper) этот flow поддерживает — у него есть stop_after_first_assistant и disallowed_tools. Именно поэтому он активен, а Node.js — fallback.
Обзор альтернатив
| Проект | Язык | Проблема 1 (orphaned) | Проблема 2 (вывод CLI) | Проблема 3 (caller-dispatched tools) |
|---|---|---|---|---|
openclaw-claude-proxy (порт 3456) |
TypeScript | ❌ — pool без SIGTERM cleanup | ❌ — stderr не форвардится | ❌ — stream-json сломан с CLI ≥ 2.1.141 |
claude-code-openai-wrapper (RichardAtCT, порт 8090) |
Python | ❌ — uvicorn без tini | ❌ — SDK pipe без логирования | ✅ — stop_after_first_assistant + disallowed_tools |
claude-code-openai-wrapper (clebermasters) |
Rust | ❌ — tokio без reaper | ❌ — pipe | ❌ — CLI сам исполняет инструменты |
claude-max-api-proxy (mnemon-dev) |
TypeScript | ❌ | ❌ | ❌ — родительский проект openclaw |
Hermes provider: claude-code |
встроен | N/A | N/A | ✅ — но упирается в rate limit OAuth API |
Ни один существующий проект не решает все три проблемы одновременно.
openclaw-claude-proxy — форки и состояние
Репозиторий mehdic/openclaw-claude-proxy:
- v1.0.8 (May 2026) — последний релиз
- 2 форка (habibtalik, ppcvote), 0 звёзд
- Нет открытых issues
- subprocess/manager.ts (311 строк):
spawn()+EventEmitter, естьdisallowedToolsв опциях, но они работают только в--printрежиме. Вstream-jsonрежиме multi-turn сломан протоколом CLI ≥ 2.1.141. kill()отправляет SIGTERM на один процесс, но глобального cleanup при SIGTERM/SIGKILL родителя нет.
clebermasters/claude-code-openai-wrapper (Rust)
Альтернатива, переписанная на Rust:
- Один статический бинарник 4.7 MB, без зависимостей времени выполнения
- Использует
tokio::process::Command+--print --output-format stream-json - Проблемы те же — запускает
claudeкак subprocess, CLI сам исполняет инструменты - Проект новый — 0 issues, 0 форков
claude-code-openai-wrapper (RichardAtCT, Python)
Единственный проект, решающий проблему 3 (caller-dispatched tools):
- Логика:
stop_after_first_assistantостанавливает итерацию после первого assistant-сообщения,disallowed_tools=['Bash','Read',...]не даёт CLI самому исполнять инструменты - Возвращает tool_use блоки как OpenAI
tool_callsдля исполнения Hermes'ом - Проблема 1 (orphaned): если запущен через uvicorn без
--init, при SIGKILL дочернийclaudeпроцесс остаётся orphan. SDKatexitне срабатывает при SIGKILL. - Проблема 2 (вывод): SDK читает stdout
claudeчерез pipe → JSON → memory channel. Человеческий вывод не транслируется. stderr CLI читается тихо в_handle_stderrи никуда не выводится.
Предложенные подходы
Против orphaned процессов (все проекты)
- Docker
--initфлаг: запускать контейнер сinit: trueв docker-compose (илиdocker run --init). Используетtiniкак PID 1 — корректно форвардит SIGTERM и перезахоранивает orphan'ов. - SIGTERM handler: в Node.js —
process.on('SIGTERM', pool.killAll()), в Python —uvicornlifespan shutdown c force kill. - Health check + restart policy:
unless-stoppedс--time=30grace period.
Против отсутствия вывода CLI
- Python SDK: добавить
stderrcallback вClaudeAgentOptions, который пишет вstderruvicorn → видно вdocker logs. - Node.js proxy: форвардить stderr
claudeв stderr родителя. - Логирование JSON: логировать каждую N-ную строку из stdout
claudeдля отладки.
Против изоляции инструментов (caller-dispatched tools)
Единственное работающее решение — Python wrapper (claude-code-openai-wrapper RichardAtCT) с:
stop_after_first_assistant = Truedisallowed_tools = ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep']permission_mode = 'bypassPermissions'
Для полного решения всех трёх проблем нужно взять Python wrapper, запустить в Docker с init: true и добавить проброс stderr из SDK.