Skip to content

Pi engine: Anthropic models are unusable under the firewall — gateway path lacks /v1, then the cache-miss guard aborts the run #58109

Description

@sigh71

Summary

With engine: { id: pi, model: anthropic/<model> } and the firewall enabled, a workflow reports success while doing nothing. Two defects stack:

  1. gh-aw's Pi provider sends OpenAI chat-completions to the api-proxy's Anthropic port at /chat/completions. The proxy forwards that path verbatim, and api.anthropic.com/chat/completions does not exist, so every request is a 404 and Pi ends its first turn with an empty assistant message.
  2. With the path corrected (workaround below), requests succeed, but Anthropic's OpenAI-compatible layer never reports prompt-cache usage, so the api-proxy's maxCacheMisses guard blocks the run after five requests with 403 max_cache_misses_exceeded.

In both cases the job exits 0, no safe outputs are produced, and the failure never reaches the PR or issue because Pi's report_incomplete write fails on a read-only mount.

Environment

  • gh-aw v0.87.10 (compiler and actions/setup scripts). actions/setup/js/pi_models_json.cjs on main (2026-09-02) has the same resolvePiApiForProvider.
  • gh-aw-firewall v0.28.10 api-proxy (ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10).
  • Model: anthropic/claude-sonnet-5. ANTHROPIC_API_KEY valid; the api-proxy's models.json lists the model as discovered from the provider.
  • Workflow also sets tools.github.mode: gh-proxy and tools.cli-proxy: true as the Pi engine requires.

Defect 1 — wrong path for Anthropic

pi-streaming.jsonl from run A:

[gh-aw/pi-models-json] awf-reflect: provider=anthropic mapped to endpoint provider=anthropic baseUrl=http://api-proxy:10001
[gh-aw/pi-models-json] resolved gateway api=openai-completions (provider=anthropic)
[gh-aw/pi-provider] provider_request provider=aw-gateway model=claude-sonnet-5 api=openai-completions method=POST url=http://api-proxy:10001/chat/completions
[gh-aw/pi-provider] provider_error provider=aw-gateway model=claude-sonnet-5 api=openai-completions status=no-response method=POST url=http://api-proxy:10001/chat/completions response_headers=none error="404 status code (no body)"

agent_end carries one assistant message with stopReason: "error", empty content. agent_output.json is {"items":[],"errors":[]}. Job exit code 0. The whole Pi process lived for about one second.

Why: resolvePiApiForProvider in pi_models_json.cjs returns openai-completions for anthropic, and its comment says the api-proxy "exposes a normalized chat-completions-style surface for every backend it fronts, including anthropic". In gh-aw-firewall v0.28.10 it does not, as far as I can see:

  • containers/api-proxy/proxy-utils.js buildUpstreamPath joins the configured base path and the incoming path and forwards it. The Anthropic port's default base path is empty.
  • anthropic-transforms.js and transforms/ implement cache-control, tool-drop and ANSI stripping. There is no OpenAI-to-Anthropic protocol translation.
  • No guard returns 404 (proxy-guards.js and guards/ send blocked responses with diagnostics and other status codes).

So the request goes upstream as https://api.anthropic.com/chat/completions. Anthropic's OpenAI-compatible endpoint is https://api.anthropic.com/v1/chat/completions. OpenAI and Copilot work because their upstreams serve chat-completions at the path the gateway sends, and gh-aw's own Pi workflows are all copilot/ or openai/ models, which is presumably why this is unreported.

Defect 2 — the cache-miss guard can never be satisfied

Workaround for defect 1 (see below) applied, run B:

provider_request  ... url=http://api-proxy:10001/chat/completions
provider_response ... status=200
(× 5)
provider_error ... error="403: {\"type\":\"max_cache_misses_exceeded\",\"message\":\"Maximum consecutive cache misses exceeded (5 / 5).\",\"consecutive_cache_misses\":5,\"max_cache_misses\":5}"

Every token_usage record from the api-proxy for those five calls has cache_read_tokens: 0, cache_write_tokens: 0 (input tokens 13,758 to 22,736 each; 32 AI credits in total). Anthropic documents that its OpenAI compatibility layer does not support prompt caching, so through this path every request is a cache miss by construction and the default maxCacheMisses of 5 ends every run after five turns. Raising max-turn-cache-misses to a large value gets past it, at the price of every turn re-sending the full context uncached — several times what the claude engine pays for the same work.

Two reporting gaps that hide both defects

  • [gh-aw/pi-provider] report_incomplete emission failed: EROFS: read-only file system, open '/home/runner/work/_temp/gh-aw/safeoutputs/outputs.jsonl' — the safe-outputs path the provider writes to is mounted read-only in the agent container, so the incomplete-run report is lost.
  • The Pi process and the job both exit 0 after a run whose only model requests failed. Nothing in the run's annotations says anything went wrong; the only visible symptom downstream is the conclusion job failing to download a detection artifact, because threat detection was skipped for an empty output.

Workaround that confirms the diagnosis

max-turn-cache-misses: 1000
engine:
  id: pi
  model: anthropic/claude-sonnet-5
  env:
    ANTHROPIC_BASE_URL: https://api.anthropic.com/v1

extractAPIBasePath in pkg/workflow/engine_api_targets.go turns the path component into --anthropic-api-base-path /v1; buildUpstreamPath does not double an existing prefix, so /v1/messages and /v1/models keep working while /chat/completions becomes /v1/chat/completions. The proxy strips the client's authorization header and injects x-api-key, so auth is fine. With both lines, run B got real responses. We have since moved these workflows to OpenAI models because of the caching cost.

Suggested fixes

  1. Route Anthropic through Pi's native anthropic-messages API against port 10001 under the firewall, as the no-firewall path already does. This fixes both defects at once: the path is right, and prompt caching (and the cache-miss guard's premise) works again.
  2. Failing that, give the Anthropic gateway base URL a /v1 suffix in pi_models_json.cjs (or have /reflect return it), and either exempt providers that cannot report cache usage from the cache-miss guard or document that max-turn-cache-misses must be raised for Anthropic on Pi.
  3. Make a run whose model requests all failed exit non-zero, and give report_incomplete a writable path in the Pi container.
  4. Correct the comment above resolvePiApiForProvider, or add an Anthropic model to the Pi smoke test so the claim is exercised.

Artifacts

Run A (defect 1): the agent artifact's pi-streaming.jsonl, agent-stdio.log, sandbox/firewall/logs/api-proxy-logs/models.json and access.log.
Run B (defect 2): the same, plus sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl showing five 200s with zero cached tokens and the 403.
Happy to attach either on request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions