Architecture
OpenClaw provider plugin that spawns Claude CLI subprocesses using Max plan OAuth instead of API keys.
Request flow
- OpenClaw gateway receives a user message
- Gateway calls GlueClaw’s
createStreamFnwith model ID and agent directory createClaudeCliStreamFnresolves the model (glueclaw-sonnet->claude-sonnet-4-6)- System prompt is scrubbed — 6 detection triggers replaced (see detection patterns)
ANTHROPIC_API_KEYis deleted from the subprocess environment, forcing Max plan OAuth- Claude CLI is spawned:
claude --dangerously-skip-permissions -p --output-format stream-json --verbose - If resuming a session:
--resume <session_id>replaces--system-prompt - NDJSON events stream back through readline:
system(init),stream_event(deltas),assistant(fallback),result(final) - Response tokens are unscrubbed — 3 renamed tokens translated back to gateway format
- Stream events emitted to OpenClaw:
start->text_delta(per chunk) ->done
Auth model
GlueClaw does not use API keys. In src/stream.ts, the subprocess environment is copied from process.env and both ANTHROPIC_API_KEY and ANTHROPIC_API_KEY_OLD are deleted. The Claude CLI then falls back to its local OAuth session from claude auth login, which uses the Max plan subscription.
The plugin registers with a synthetic auth key (glueclaw-local) so OpenClaw’s auth system is satisfied without real credentials.
Resiliency
- Request timeout — each stream has a 120s timeout (
REQUEST_TIMEOUT_MS). If the CLI hangs, the process is killed and an error event emitted. - SIGKILL fallback — after SIGTERM, a 5s grace period allows cleanup. If the process is still alive, SIGKILL is sent.
- Atomic session writes —
sessions.jsonis written to a.tmpfile then renamed, preventing corruption on crash. - Session eviction — the session map is capped at 1000 entries. Oldest sessions are evicted on overflow.
- Stderr capture — CLI stderr is collected and included in error events for diagnostics (auth failures, rate limits).
- Concurrent safety — parallel streams with different session keys are independent. Same-key concurrent access uses last-writer-wins semantics without corruption.
Detection scrubbing
OpenClaw’s system prompt contains tokens that Anthropic’s API rejects. scrubPrompt() rewrites them before sending to the CLI, unscrubResponse() reverses them in responses.
Scrub (prompt -> CLI):
| Pattern | Replacement |
|---|---|
personal assistant running inside OpenClaw | ...inside GlueClaw |
HEARTBEAT_OK | GLUECLAW_ACK |
reply_to_current | reply_current |
[[reply_to: | [[reply: |
openclaw.inbound_meta | glueclaw.inbound_meta |
generated by OpenClaw | generated by GlueClaw |
Unscrub (response -> gateway):
| Pattern | Replacement |
|---|---|
GLUECLAW_ACK | HEARTBEAT_OK |
reply_current | reply_to_current |
[[reply: | [[reply_to: |
Session persistence
Sessions enable multi-turn conversation memory across separate requests.
- Storage:
~/.glueclaw/sessions.json— a JSON object mapping session keys to Claude session IDs - Key format:
glueclaw:{agentDir}(e.g.,glueclaw:default) - Capture: Session ID is extracted from NDJSON
system/initandresultevents - Resume: When a session key exists,
--resume <session_id>is passed instead of--system-prompt - Working directory: CLI runs from
~/.glueclawso Claude’s own session data persists
MCP bridge
Gives the Claude CLI subprocess access to OpenClaw gateway tools (message, sessions, memory, web search, etc.).
install.shpatches OpenClaw’sserver-*.jsto expose__GLUECLAW_MCP_PORTand__GLUECLAW_MCP_TOKENas env vars when the MCP loopback server startsgetMcpLoopback()reads these env vars at runtimewriteMcpConfig()creates a temporarymcp.jsonpointing tohttp://127.0.0.1:{port}/mcpwith auth headers- CLI is invoked with
--strict-mcp-config --mcp-config {path} - Temp config is cleaned up in a
finallyblock
Installer
install.sh runs 7 idempotent steps:
| Step | What it does |
|---|---|
| 1 | npm install — project dependencies |
| 2 | Adds GLUECLAW_KEY=local to shell profile |
| 3 | Registers plugin with openclaw plugins install --link (fallback: manual config) |
| 4 | Configures 3 models, sets default to glueclaw-sonnet, allows gateway tools |
| 5 | Writes auth profile to ~/.openclaw/agents/main/agent/auth-profiles.json |
| 6 | Patches server-*.js to expose MCP loopback port and token via env vars |
| 7 | Starts gateway on port 18789, waits for readiness |
Re-run after OpenClaw updates to re-apply the MCP patch.
Source files
| File | Purpose |
|---|---|
index.ts | Plugin entry: provider registration, model catalog, stream function wiring |
src/stream.ts | Core: subprocess spawn, NDJSON parsing, scrub/unscrub, sessions, MCP bridge |
src/openclaw.d.ts | Type declarations for the OpenClaw plugin SDK |
openclaw.plugin.json | Plugin manifest: provider ID, auth env vars, auth choices |
install.sh | Installer: deps, config, auth, MCP patch, gateway startup |
vitest.config.ts | Test runner config (forks pool, 30s timeout) |
Test coverage
61 automated tests across 3 layers. See testing for details.
| Layer | Tests | What it validates |
|---|---|---|
| Unit | 37 | scrubPrompt, unscrubResponse, buildUsage, buildMsg, getMcpLoopback, writeMcpConfig |
| Integration | 17 | Mock CLI (11 NDJSON scenarios), request timeout, stderr capture, 4 concurrency tests |
| E2E | 7 | Real Claude CLI with Max plan OAuth, session resume, OpenClaw plugin registration |