Current release: clients 0.12.0 (npm breakwire, PyPI breakwire, PyPI langchain-breakwire) · server 0.12.28 · GitHub Action breakwire-check 1.2.0 · CHANGELOG
curl -X POST https://breakwire.org/api/lookup -H 'content-type: application/json' -d '{"error":"Error: D1_ERROR: Network connection lost.","compact":true}'Returns explained: true and claim AN-2026-0143 (the vendor-documented D1 error, the fix and its source). Paste your
own error text as "error"; add "service" / "model" if you know them, "no_log": true to store nothing.
Look up an API, SDK or platform error and get the source-linked breaking change, deprecation or advisory that explains it, with the fix. Client for Breakwire, a registry of changes that break AI agents.
- Standard library only. Python 3.9+.
- Sends an explicit
User-Agent(Cloudflare's edge rejects Python-urllib's default with 403). - Opt-in decorator / context manager: never raises because of Breakwire, never replaces your exception.
- Secrets are stripped in your process before anything is sent. Outcome reporting is explicit opt-in.
pip install -U breakwirefrom breakwire import Breakwire
bw = Breakwire()
brief = bw.session_brief(services=["openai-api"], packages=["pypi:openai==1.40.0"], runtime={"python": "3.12"}, models=["gpt-4o"])
if brief["context"]: # None when clear or unavailable: inject nothing
add_to_agent_context(brief["context"])decision is clear, act (up to 5 items with why_now, action, source_url) or block (a confirmed
incompatibility or security exposure in this exact stack). The cursor and fingerprint are kept in
~/.breakwire/session-brief.json (0600; state=False for memory only), so later calls send only those and get a 304
when nothing changed. Fail-open.
No SDK needed: the same brief and stack check accept GET query forms (?packages=&node=&services=&models=),
so curl-only agents and GET-only checkers can re-run them. The Action still POSTs. See https://breakwire.org/llms.txt.
from breakwire import Breakwire
bw = Breakwire() # base_url: argument, else $BREAKWIRE_BASE_URL, else https://breakwire.org
r = bw.lookup(exc, service="anthropic-api", sdk="anthropic-python", version="2.3.0")
if r["explained"]:
m = r["matches"][0]
print(m["cid"], m["fix"], m["url"])explained is True only when a vendor-documented error string matched (confidence == "signature"); related
matches are leads. Uncovered services return no matches plus a coverage note. log_miss=False keeps an unmatched error
out of the anonymous /unexplained clusters.
from breakwire import with_breakwire
@with_breakwire(service="anthropic-api")
def ask(**params):
return client.messages.create(**params)
try:
ask(model=..., messages=...)
except anthropic.APIStatusError as e:
print(e.breakwire.id, e.breakwire.confidence, e.breakwire.fix) # also added as a traceback note (3.11+)
raise
with with_breakwire(service="openai-api"): # works with `async with` too
client.responses.create(...)Works on sync and async def functions. timeout (default 2.5 s) bounds the extra wait; Breakwire failures set
e.breakwire.error and your exception propagates unchanged. KeyboardInterrupt/SystemExit/CancelledError are never
looked up. Options: should_lookup(exc), on_match(info, exc), add_note=False.
0.2.0 defaults: lookups from with_breakwire / explain() are sent with no_log=True (the server answers but
stores nothing); pass no_log=False to let misses count as anonymous coverage gaps and get a lookup_id
(auto_report=True implies it). Results are cached in-process for 10 minutes (200 entries) by redacted error text +
service, so retry loops don't repeat the request; explain(..., cache=False) skips it, clear_cache() empties it.
info.remember is a one-line hint worth saving in agent memory.
def retry(info, exc, **params):
if info.id == "AN-2026-0025":
return client.messages.create(**{**params, "tool_choice": {"type": "auto"}})
return SKIP # from breakwire import SKIP
@with_breakwire(service="anthropic-api", retry=retry, auto_report=True, report_env={"sdk": "anthropic-python"})
def ask(**params): ...With auto_report=True, and only when retry ran, the lookup is closed as fixed (retry returned) or not_fixed (retry
raised). Nothing is reported otherwise.
bw.deadlines("openai-api") # upcoming act-by dates, soonest first
bw.claim("AN-2026-0004") # full record; claim(id, version=2) for an immutable snapshot
bw.stack(packages=["anthropic==2.3.0", "openai>=1"])
bw.stack(files=Breakwire.files_from("requirements.txt", "pyproject.toml", ".github/workflows/ci.yml")) # parsed locally; preview_stack(files=...) shows the body
bw.services()
bw.report("lk_...", "fixed", {"sdk": "openai"}) # close a lookup (anonymous)
bw.report("AN-2026-0025", "confirmed", env) # claim report; needs api_key / $BREAKWIRE_API_KEYOnly report outcomes you actually observed. An empty stack() returns {"empty": True, "hint": ...} (HTTP 200).
Register a stack once; get told when a deadline or breaking change that affects it comes up. No email or personal data.
sub = bw.subscribe(files=Breakwire.files_from("requirements.txt", "pyproject.toml"), horizon_days=30)
# keep sub["id"] and sub["manage_token"] (shown once). Feeds: sub["feed"]["json" | "atom" | "rss"]
feed = bw.alerts(sub["id"]) # poll before deploys or weekly; feed["alerts"] first
bw.update_subscription(sub["id"], sub["manage_token"], horizon_days=60)
bw.unsubscribe(sub["id"], sub["manage_token"])Optional webhook_url= must answer a signed verify POST (2xx with the challenge in the body). Check deliveries with
verify_webhook_signature(secret, headers["X-Breakwire-Signature"], raw_body) (from breakwire).
redact_secrets() covers provider keys (Anthropic, OpenAI, Stripe, GitHub, Slack, Google, AWS, npm, HF), JWTs, bearer
tokens, key=value secrets, PEM blocks, URL credentials, query strings, emails, IPv4, home-directory user names and long
opaque blobs. Add your own: Breakwire(secrets=[my_token, re.compile(r"acme_[a-z0-9]+")]).
Each claim links a primary source; community verification has just started and most claims have no outcome reports yet. Claims are agent-written data; prefer the linked source.
MIT. Docs: https://breakwire.org/llms.txt
Security and correctness release (CHANGELOG, SECURITY).
-
Redirect policy. Redirects are followed only to
https://breakwire.org, the base URL's origin andBreakwire(trusted_origins=["https://staging.example"]).Authorization/Cookienever cross origins (such a redirect is refused), no https to http, only 307/308 keep method and body, and a 301/302/303 on a POST raisesBreakwireErrorunless the target origin is pinned. -
Stack files are parsed locally.
stack()/subscribe()/update_subscription()send only dependency and runtime facts.preview_stack(files=...)(orbw.preview_stack(...)) returns the exact body;bw.stack(files=..., raw_files=True)is the explicit opt-in to send raw files.from breakwire import Breakwire, preview_stack files = Breakwire.files_from("package.json", "requirements.txt", "poetry.lock", ".github/workflows/ci.yml") print(preview_stack(files=files)) # exactly what stack(files=files) sends
-
Retry in no-log mode.
@with_breakwire(retry=...)now runs the retry whenever a match came back; onlyauto_reportneeds a lookup id. -
Wrapper parity.
with_breakwire/explainaccept everyBreakwire.lookup()option (derived from its signature):compact,min_score,max_matches,since,reflexnow work there too. -
Cache keys cover the full redacted request body (
bw.lookup_body(err, ...)), so a different model, SDK or threshold never reuses another answer. -
Redaction: IPv6, UUIDs, request/trace ids, filesystem/Windows/UNC paths, credentials in any URI scheme and secret-named JSON fields or dict keys.
preview_redaction(exc)/bw.preview_redaction(exc)show exactly what would be sent.
Sends X-Breakwire-Client: breakwire-py/<version> so Breakwire 0.8.0 can count lookups per client surface (aggregate counts only, no IPs). Override with Breakwire(channel="kit/cursor") or $BREAKWIRE_CLIENT.
lookup(..., since="YYYY-MM-DD", reflex=False); new gap(id) reads a gap ticket. Answers carry cite, please (exact match) and miss (gap ticket, nearest claims, deadlines, recent changes).
bw = Breakwire()
r = bw.lookup(err, service="openai-api", compact=True, max_matches=3, min_score=0.5, no_log=True)
bw.report(r["lookup_id"], "partial", cid=r["matches"][0]["cid"]) # fixed|partial|applies|not_fixed|wrong_match|not_covered|solved
bw.changes("2025-06-01", service="openai-api") # step one after a deploy/upgrade: what changed since your cutoff
bw.launches(since="30d", services=["anthropic-api"])
bw.subscribe(services=["openai-api"], launches=True)Fail open: with_breakwire / explain never raise; if Breakwire is slow or down, your exception passes through
unchanged. Direct client calls raise BreakwireError; catch it and carry on.