Skip to content

feat(warp): run agents through a CONNECT proxy instead of ANTHROPIC_BASE_URL - #156

Merged
teamchong merged 7 commits into
mainfrom
feat/warp
Jul 28, 2026
Merged

feat(warp): run agents through a CONNECT proxy instead of ANTHROPIC_BASE_URL#156
teamchong merged 7 commits into
mainfrom
feat/warp

Conversation

@teamchong

@teamchong teamchong commented Jul 28, 2026

Copy link
Copy Markdown
Owner

ANTHROPIC_BASE_URL is visible to the agent. Claude Code checks it and disables
client-side features when it points anywhere but api.anthropic.com, and OAuth
paths sign against the URL they were given.

pxpipe warp removes the env var from the equation.

Before:

ANTHROPIC_BASE_URL=http://127.0.0.1:47821 claude   # agent sees the override

After:

pxpipe warp -- claude                              # agent sees api.anthropic.com

The child gets HTTPS_PROXY pointed at a loopback CONNECT listener on a
kernel-assigned port, ANTHROPIC_BASE_URL deleted, and the warp CA injected
through NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, CURL_CA_BUNDLE, and
REQUESTS_CA_BUNDLE. Trust is scoped to that child; nothing is installed into a
system or keychain store.

The CA lives at ~/.pxpipe/warp-ca.pem with its key at ~/.pxpipe/warp-ca-key.pem
(mode 0600), created on first run and reused afterwards, so a re-run does not
invalidate anything already trusted.

Only api.anthropic.com/v1/messages* is decrypted and diverted to the running
pxpipe proxy. Every other host is tunneled blind at CONNECT, so OAuth, telemetry,
/remote-control, and claude.ai connectors are untouched. The
ANTHROPIC_BASE_URL flow still works unchanged.

Ported from wardex (proxy.go, ca.go) with two narrowings: warp decrypts only
hosts a route could match rather than every host, and it forwards matches to the
pxpipe already running rather than starting a second transforming proxy.

No automated coverage yet. Verified manually with claude, shell aliases, and curl.

Also fixes @pxpipe <cmd> never being answered outside opencode.

codex posts to /v1/responses and /v1/chat/completions, which the pin handler did
not recognize, so the command reached the model as plain text.

Claude Code appends a system-role agent catalogue after the user's turn and hides
the first typed line behind the CLAUDE.md envelope, so the pin-only check never
matched. Only trailing system-role metadata is skipped; a trailing tool_result or
assistant prefill still forwards.

Verified: oc, cx, cc all answer @pxpipe pin locally with no upstream call.

Fixes #141
Fixes #60

…ASE_URL

ANTHROPIC_BASE_URL is visible to the agent, which disables client-side
features when it points anywhere but api.anthropic.com.

warp gives the child HTTPS_PROXY on an ephemeral loopback port and a
per-run CA, then decrypts only /v1/messages and tunnels every other host
blind. The agent believes it is talking to api.anthropic.com.

Ported from wardex proxy.go/ca.go.
Comment thread src/warp/index.ts Fixed
Four defects found reviewing the CONNECT path:

- SAN extension emitted the host as a bare string, so leaf certs failed
  hostname verification on Node 20+. Wrap it in a DNSName choice.
- Client aborts left the upstream socket open, leaking a connection per
  cancelled request. Tear down both halves on either side closing.
- Forwarded requests dropped the original Host, so upstreams that route
  on it returned the wrong vhost. Preserve it.
- Stale comments described the pre-CONNECT env var design.
Pin injection already worked for codex: the proxy rewrites the request
and relocates file pins last. Only the command reply was gated on
isAnthropicMessagesPath, so `@pxpipe pin` was forwarded to the model,
which then guessed at what it meant.

Add chat/completions and /responses synthesizers so the reply streams
in the shape each client expects, and widen the gate.

Requests whose last item is a tool result are left alone, so a pin
command earlier in the transcript cannot replay mid tool loop.
The resolver asks the interactive shell first because an alias can shadow
a real binary: `cc` is Apple clang on PATH and a Claude Code alias in the
user's zsh. But it trusted the alias unconditionally, so an alias whose
target had been uninstalled killed the launch with a working binary on
PATH.

Before:
  alias claude=/opt/homebrew/bin/claude   # removed, now a mise install
  $ pxpipe warp -- claude
  zsh:1: no such file or directory: /opt/homebrew/bin/claude

After:
  [pxpipe] warp: ignoring stale alias claude → /opt/homebrew/bin/claude
  (runs the claude on PATH)

A live alias still wins over PATH. An env-assignment prefix
(`FOO=1 claude`) cannot be checked, so it is still taken at its word.
Claude Code appends a system-role message (its agent-type catalogue) after the
user's turn, and the user's first typed line rides behind the CLAUDE.md
envelope. isPinOnlyRequest inspected only the literal last element, and
isCommandOnlyTurn rejects both a non-user role and a leading reminder, so every
cc pin command went upstream unanswered: the pin was folded into later requests
but the user never got a status reply.

Before:
  messages = [ user:   "<system-reminder>CLAUDE.md...</system-reminder>@pxpipe pin",
               system: "Available agent types for the Agent tool:..." ]
  -> forwarded to the model

After:
  -> answered locally, no upstream call

liveTurn() skips only trailing system-role metadata. A trailing tool_result or
assistant prefill still forwards, so a mid-tool-loop turn is never hijacked.

The envelope is tolerated on the live path only. Answering locally forwards
nothing and the client resends message 0 intact, but rewriting it would drop the
whole block, so the outbound strip path still rejects it and CLAUDE.md survives.
README says the dashboard carries the full instructions; it carried none.
Adds a "Connect an agent" panel next to the model-scope panel: warp
invocations, alias and --route notes, the manual ANTHROPIC_BASE_URL
fallback, and the @pxpipe pin/unpin commands including the file-backed
CLAUDE.md / AGENTS.md tier that unpin cannot reach.
@teamchong teamchong changed the title warp: run agents through a CONNECT proxy instead of ANTHROPIC_BASE_URL feat(warp): run agents through a CONNECT proxy instead of ANTHROPIC_BASE_URL Jul 28, 2026
@teamchong
teamchong merged commit 930f715 into main Jul 28, 2026
5 of 6 checks passed
@teamchong
teamchong deleted the feat/warp branch July 28, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants