Tawa docs

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

VariableDefaultMeaning
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_URLhttps://gateway.tawaguard.comWhere scanning actually runs. Override for local development against a local-stack gateway.
TAWA_PROXY_LISTEN_ADDR127.0.0.1:8788Where 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_URLhttps://api.openai.com/v1Upstream the proxy forwards to. Already includes /v1 — see Quickstart for why that matters. Override for testing against a mock.
TAWA_PROXY_FAIL_MODEopenWhat happens when the scan call to Tawa itself fails — see below.
TAWA_PROXY_BLOCK_ACTIONSBLOCK,HARD_BLOCK,SANDBOXComma-separated verdict tiers that stop a request, action item, or reply — see What triggers a block.
TAWA_PROXY_SCAN_OUTPUT_TEXTtrueScan the model's own visible reply (message items) before it reaches Codex CLI — see Scanning the model's own reply.
TAWA_PROXY_OUTPUT_CHUNK_CHARS400Streaming checkpoints vs. standard (whole-reply) scanning for message output — see Streaming checkpoints vs. standard mode.
RUST_LOGinfoStandard 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_BLOCK

An 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=false

An 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=0

This 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.

On this page