# Fail-open (/docs/concepts/fail-open)



**Golden rule: TokenPolice never breaks your app.** A firewall that takes your app down when
*it* has a problem is worse than no firewall — so if anything goes wrong on TokenPolice's side,
the SDK swallows it and **lets your LLM call proceed**.

## What "fail-open" means in practice [#what-fail-open-means-in-practice]

If the collector is slow, unreachable, misconfigured, or returns an error — a timeout, a
network blip, a bad API key — the SDK does not raise. It logs the problem (if you asked it to)
and your call runs exactly as it would without TokenPolice. Your users never see a TokenPolice
error because of *TokenPolice's* trouble.

## The one exception [#the-one-exception]

There is exactly one error the SDK will ever throw into your code: &#x2A;*`TokenPoliceBlockedError`**.
It's raised only when a rule *you configured* decides to block **and** both dials are set to
enforce ([dry-run vs enforce](/docs/concepts/dry-run-vs-enforce)). That's not a failure — it's
the firewall doing the job you asked for.

```python
try:
    reply = run_agent(...)
except tp.TokenPoliceBlockedError as e:
    # a rule you set is enforcing and chose to block this call
    return "You've hit your usage limit for now."
```

The error carries structured fields so you can react precisely: `reason`, `rule_id`, `kind`
(which rule type fired), and `trace_id`. Full contract in
[Errors](/docs/sdk/errors).

<Callout type="warn">
  `TokenPoliceBlockedError` is a *deliberate* block, not a fault. Everything else —
  every timeout, outage, or config mistake — is silently allowed through. If you're seeing this
  error, a rule wanted to stop the call.
</Callout>

<Callout type="info">
  One deliberate exception, the other way round: &#x2A;*on a long-running server, a block already in
  force stays in force.** If a user or conversation has already been blocked and TokenPolice
  then goes unreachable, that block holds under enforce — an outage can't be used to lift a
  limit you set. New decisions still fail open.

  This depends on the SDK holding an open stream of rule and blocked-set updates, which it does
  only when `deployment` resolves to `"daemon"`. On `"serverless"` and `"edge"` there is no
  stream — every decision is an inline `/check`, so during an outage *everything* fails open,
  including a block that was already in force. If that matters for your workload, keep the
  enforcing path on a daemon deployment.
</Callout>

## Next [#next]

<Cards>
  <Card title="Errors" href="/docs/sdk/errors" description="The full TokenPoliceBlockedError contract and its fields." />

  <Card title="Dry-run vs enforce" href="/docs/concepts/dry-run-vs-enforce" description="The two dials that decide whether a block ever fires." />
</Cards>
