Summary
With engine: { id: pi, model: anthropic/<model> } and the firewall enabled, a workflow reports success while doing nothing. Two defects stack:
- 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.
- 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
- 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.
- 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.
- Make a run whose model requests all failed exit non-zero, and give
report_incomplete a writable path in the Pi container.
- 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.
Summary
With
engine: { id: pi, model: anthropic/<model> }and the firewall enabled, a workflow reports success while doing nothing. Two defects stack:/chat/completions. The proxy forwards that path verbatim, andapi.anthropic.com/chat/completionsdoes not exist, so every request is a 404 and Pi ends its first turn with an empty assistant message.maxCacheMissesguard blocks the run after five requests with403 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_incompletewrite fails on a read-only mount.Environment
actions/setupscripts).actions/setup/js/pi_models_json.cjsonmain(2026-09-02) has the sameresolvePiApiForProvider.ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10).anthropic/claude-sonnet-5.ANTHROPIC_API_KEYvalid; the api-proxy'smodels.jsonlists the model as discovered from the provider.tools.github.mode: gh-proxyandtools.cli-proxy: trueas the Pi engine requires.Defect 1 — wrong path for Anthropic
pi-streaming.jsonlfrom run A:agent_endcarries one assistant message withstopReason: "error", empty content.agent_output.jsonis{"items":[],"errors":[]}. Job exit code 0. The whole Pi process lived for about one second.Why:
resolvePiApiForProviderinpi_models_json.cjsreturnsopenai-completionsforanthropic, 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.jsbuildUpstreamPathjoins the configured base path and the incoming path and forwards it. The Anthropic port's default base path is empty.anthropic-transforms.jsandtransforms/implement cache-control, tool-drop and ANSI stripping. There is no OpenAI-to-Anthropic protocol translation.proxy-guards.jsandguards/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 ishttps://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 allcopilot/oropenai/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:
Every
token_usagerecord from the api-proxy for those five calls hascache_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 defaultmaxCacheMissesof 5 ends every run after five turns. Raisingmax-turn-cache-missesto a large value gets past it, at the price of every turn re-sending the full context uncached — several times what theclaudeengine 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.conclusionjob failing to download adetectionartifact, because threat detection was skipped for an empty output.Workaround that confirms the diagnosis
extractAPIBasePathinpkg/workflow/engine_api_targets.goturns the path component into--anthropic-api-base-path /v1;buildUpstreamPathdoes not double an existing prefix, so/v1/messagesand/v1/modelskeep working while/chat/completionsbecomes/v1/chat/completions. The proxy strips the client'sauthorizationheader and injectsx-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
anthropic-messagesAPI 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./v1suffix inpi_models_json.cjs(or have/reflectreturn it), and either exempt providers that cannot report cache usage from the cache-miss guard or document thatmax-turn-cache-missesmust be raised for Anthropic on Pi.report_incompletea writable path in the Pi container.resolvePiApiForProvider, or add an Anthropic model to the Pi smoke test so the claim is exercised.Artifacts
Run A (defect 1): the
agentartifact'spi-streaming.jsonl,agent-stdio.log,sandbox/firewall/logs/api-proxy-logs/models.jsonandaccess.log.Run B (defect 2): the same, plus
sandbox/firewall/logs/api-proxy-logs/token-usage.jsonlshowing five 200s with zero cached tokens and the 403.Happy to attach either on request.