CedarCopilot wants to merge 3 commits into staging from fix/exa-credits-alert-and-search-fallback
Live on prod, no production signal yet
Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.
Exa backs both web-search and web-fetch, so an exhausted credit balance takes every research path down at once , meeting prep company research, socials and e-commerce detection, brand/news lookups, outbound trigger research. It fails silently from the user's side: the agent just reports it found nothing.
That is exactly what happened in August. web-search/web-fetch went to zero successes from Aug 28 17:00 UTC and stayed there for three days (165 calls, 0 ok) before anyone noticed , and it was found while testing something unrelated, not by any alarm.
Three separate defects made a three-day silent outage possible.
1. Nothing was watching for it. NO_MORE_CREDITS on either Exa endpoint now raises one internal Slack alert. Rate-limited on the endpoint alone , credit exhaustion is an account-wide condition, so keying it per-user or per-call would have produced hundreds of messages. Fire-and-forget, so alerting can never turn a degraded lookup into a thrown request.
2. The web-search fallback had never once worked. It asks runCodeExecutorTool for bare JSON; the executor returns it wrapped in a ```json fence, as models reliably do; JSON.parse died on the leading backtick and threw away results it had already fetched. Confirmed in CloudWatch across the whole outage window:
[webSearchTool] Fallback also failed: SyntaxError: Unexpected token '`', "```json
Now strips the fence before parsing. The resilience path this tool was built around has been dead the entire time it has existed.
3. The failure was undiagnosable from the tool result. The catch block kept neither underlying error, recording only "Web search failed (both Exa and fallback)". That is why web-fetch surfaced NO_MORE_CREDITS and web-search told us nothing, and why diagnosing this needed CloudWatch for a question the tool result should have answered. Both causes now travel with the logged result as exaError and fallbackError.
pnpm --filter @zero/server run types , clean/contents and /search verified returning results post-top-upLow. No behaviour change on the success path. The alert is additive and fire-and-forget; the fence strip only widens what already-fetched results get accepted; the error fields are additive on a result that was already a failure.
š¤ Generated with Claude Code
This PR adds Slack notification support for exhausted Exa credits, repairs parsing of JSON-fenced fallback search results, and records both primary and fallback errors.
The normal fallback failure path must be fixed before merging because it still reports an empty success and drops the diagnostic causes this PR intends to preserve.
runCodeExecutorTool reports ordinary failures as returned values, but webSearchTool only constructs its new dual-error result when an exception is thrown; the new alert throttle also cannot suppress duplicate notifications across server processes.
Files Needing Attention: apps/server/src/mastra/tools/research/webSearchTool.ts; apps/server/src/services/notifications/internal/slack-notifier.ts
| Filename | Overview |
|---|---|
| apps/server/src/mastra/tools/research/webSearchTool.ts | Adds fence stripping and richer dual-failure diagnostics, but normal executor failure results bypass the diagnostic catch and remain false successes. |
| apps/server/src/services/enrichment/exa.ts | Detects exhausted-c |
Libra has not measured any production surfaces for this change yet.