Configuration
Every environment variable tawa-codex-proxy reads, how blocking is decided, and what SDK-level options aren't wired up here yet.
The proxy is configured entirely through environment variables — there's no config file. Variable names match the Claude Code relay's wherever the concept is shared, so both proxies have a consistent config surface if you're running them side by side.
Environment variables
| Variable | Default | Meaning |
|---|---|---|
TAWA_API_KEY | (required) | Your Tawa API key, sent to the scan endpoint. Separate from OPENAI_API_KEY — this one is never sent to OpenAI, and your real OpenAI key is never sent to Tawa. |
TAWA_GATEWAY_URL | https://gateway.tawaguard.com | Where scanning actually runs. Override for local development against a local-stack gateway. |
TAWA_PROXY_LISTEN_ADDR | 127.0.0.1:8788 | Where the proxy itself listens. Different from the Claude Code relay's 8787 default on purpose, so both can run at once. |
TAWA_PROXY_OPENAI_BASE_URL | https://api.openai.com/v1 | Upstream the proxy forwards to. Already includes /v1 — see Quickstart for why that matters. Override for testing against a mock. |
TAWA_PROXY_FAIL_MODE | open | What happens when the scan call to Tawa itself fails — see below. |
TAWA_PROXY_BLOCK_ACTIONS | BLOCK,HARD_BLOCK,SANDBOX | Comma-separated verdict tiers that stop a request, action item, or reply — see What triggers a block. |
TAWA_PROXY_SCAN_OUTPUT_TEXT | true | Scan the model's own visible reply (message items) before it reaches Codex CLI — see Scanning the model's own reply. |
TAWA_PROXY_OUTPUT_CHUNK_CHARS | 400 | Streaming checkpoints vs. standard (whole-reply) scanning for message output — see Streaming checkpoints vs. standard mode. |
RUST_LOG | info | Standard tracing-subscriber log level filter — not Tawa-specific, but useful for debug-level troubleshooting. |
Fail-open vs. fail-closed
TAWA_PROXY_FAIL_MODE decides what happens if the call to Tawa's scan
endpoint fails — not what happens when a scan succeeds and returns a
block, which always blocks regardless of this setting.
open(default): if the scan call itself fails, the request forwards to OpenAI anyway. A Tawa outage degrades you back to unprotected Codex CLI rather than a broken one.closed: if the scan call fails, the request is rejected instead of forwarded.
What triggers a block
By default, a scan verdict of BLOCK, HARD_BLOCK, or SANDBOX stops
it — an OpenAI-shaped 400 error on the request side, or a synthetic
mid-stream error SSE event on the response side that ends the stream
before a blocked action or reply reaches the client. Every other verdict
(ALLOW, REVIEW, SOFT_BLOCK, AMBIGUOUS, OOD) forwards through by
default — same reasoning as the Claude Code relay: there's no human in a
local CLI's loop to hand an ambiguous verdict to.
REVIEW and the review flag
The relay blocks or forwards on the action the gateway returns. The
gateway collapses an engine REVIEW to ALLOW or BLOCK per your app's
setting for each content type, so a REVIEW on a type set to block
arrives as BLOCK and stops, whatever TAWA_PROXY_BLOCK_ACTIONS says. By
default that applies to user prompts and tool calls, and not to model
replies. To let such results through for an app, set that content type to
allow in the dashboard. TAWA_PROXY_BLOCK_ACTIONS=...,REVIEW only has an
effect against a gateway that predates the
review flag.
Override which tiers actually block with TAWA_PROXY_BLOCK_ACTIONS — a
comma-separated list of any of ALLOW, REVIEW, SOFT_BLOCK, BLOCK,
HARD_BLOCK, SANDBOX, AMBIGUOUS, OOD:
# Also stop on SOFT_BLOCK and REVIEW, not just the hard-block family:
TAWA_PROXY_BLOCK_ACTIONS=BLOCK,HARD_BLOCK,SANDBOX,SOFT_BLOCK,REVIEW
# Narrower than the default — only a HARD_BLOCK stops anything:
TAWA_PROXY_BLOCK_ACTIONS=HARD_BLOCKAn unset or blank value falls back to the default set. An unrecognized entry (a typo, or a future verdict tier this proxy doesn't know about yet) is silently ignored rather than rejected at startup.
This is a proxy-local decision about which verdict tiers stop a request — it's separate from (and not a substitute for) the detection policy itself. Policy Studio rules and PII settings are resolved server-side from your API key's project, and this proxy never touches them.
Scanning the model's own reply
By default, the model's own visible reply (any message output item) is
scanned too — before it reaches Codex CLI. This catches a different threat
shape than action gating: a hijacked model can exfiltrate something it read
(a secret in a file, credentials from a tool result) just by printing it in
its answer, with no action item involved at all.
Turn it off entirely if that's not worth it for a given workflow:
TAWA_PROXY_SCAN_OUTPUT_TEXT=falseAn empty reply (or one that's all whitespace) is never sent for scanning regardless of this setting. Action-item gating is unaffected either way; this only controls the text side.
Streaming checkpoints vs. standard mode
Once output-text scanning is on, TAWA_PROXY_OUTPUT_CHUNK_CHARS decides
how it's scanned — the same cost/delay trade-off as
the Claude Code relay,
applied to message items instead of text content blocks:
- Streaming checkpoints (default,
400): every time roughly that many new characters have streamed in, the proxy scans everything accumulated so far and releases whatever's cleared to Codex CLI. One scan call per checkpoint. - Standard mode (
TAWA_PROXY_OUTPUT_CHUNK_CHARS=0): no mid-stream checkpoints — the entire reply is held until the model finishes generating it, scanned exactly once, then released or blocked as a single piece.
# Smaller checkpoints: closer to un-gated streaming, more scan calls
TAWA_PROXY_OUTPUT_CHUNK_CHARS=150
# Standard mode: one scan per reply, but nothing streams until it's done
TAWA_PROXY_OUTPUT_CHUNK_CHARS=0This is a real cost multiplier, not just a latency knob
Every checkpoint is its own scan call against your Tawa account, same as the Claude Code relay — see that page's callout on this for the numbers.
Action items (function_call and everything else this proxy gates
generically) are never chunked regardless of this setting — each is scanned
exactly once, at response.output_item.done, since there's no
per-item-type partial-JSON checkpoint that would mean anything across ~30
different item shapes.
Session correlation
Not implemented. The Claude Code relay forwards Claude Code's own
x-claude-code-session-id header through to Tawa as session_id,
confirmed from Claude Code's own docs. There's no confirmed equivalent
header for Codex CLI, so this relay doesn't guess at one — every scan from
it passes no session identifier. Worth adding once the actual mechanism (if
any) is confirmed against a real Codex CLI session.
Not yet configurable here
Scope note
Same gap as the Claude Code relay: the TypeScript and Python SDKs expose several per-call options this proxy doesn't thread through, because its only configuration surface is the environment variables above, set once at process start.
- Per-user identifiers, Policy Studio directives, language hints, and a fully custom block predicate — none of these are available through this proxy yet. If you need any of them today, use the TypeScript or Python SDK directly.