See it in action
Codela caught its own AI agent.
Codela watches its own repo. A change written with Claude Code made Codela's production slower. Here's what Codela did next.
Service codela-api · monitored with SigNoz · deployed on Railway
Step 1
A deploy goes out
077d769 Product analytics with PostHog: what people do in Codela, and what converts
written with Claude Code
Codela waited for enough traffic after the deploy, then compared production with the same length of time before it, for the whole service and for each endpoint.
Step 2
Codela sees production got slower
p95 latency
22ms → 520ms
p99 latency
27ms → 623ms
Error rate
0.00% → 0.00%
| Endpoint after the deploy | p95 | |
|---|---|---|
| POST /ingest/<path> | 619ms | new: relays browser analytics to PostHog |
| GET /ingest/<path> | 158ms | new |
| GET /health | 1ms | unchanged |
| GET /lessons | 3ms | unchanged |
The rest of the app was fine. One new endpoint was slow, and there was enough traffic through it to drag up the whole service.
Step 3
It works out why, and writes the lesson
Codela read the deploy's diff alongside the numbers, decided the change caused it, and wrote this lesson:
.codela/lessons/synchronous-third-party-analytics-relay-inside-api-477b29.md
Synchronous third-party analytics relay inside API request path [medium]
/ingest/* relays browser analytics to PostHog in-request (p95 619ms); proxied third-party latency holds API workers and dominates service p95.
Read the full lesson
What went wrong
The deploy added a first-party proxy (`/ingest/*`) so posthog-js events reach PostHog through our domain. Each relayed request waits in the Flask worker for a full round trip to us.i.posthog.com (or us-assets) before responding. POST /ingest takes 43ms at p50, 619ms at p95 and up to 774ms. Because browser analytics is now the highest-volume endpoint (64 of the requests sampled), its third-party latency becomes the service's p95 (22ms → 520ms). Existing endpoints did not slow down. The server-side capture() path in analytics.py correctly uses a background queue; the relay does not, and cannot fully, because the browser expects PostHog's response.
Why it matters
Analytics traffic scales with page views and session replays rather than with product usage. Every relayed event holds a worker for up to ~0.8s. Under a traffic spike, or if PostHog slows down, low-value telemetry can exhaust the worker pool and queue real API requests (billing, dashboard, webhooks) behind it.
Applies when
- Adding a reverse proxy or relay endpoint in the API process that forwards client traffic to a third-party (analytics, error reporting, CDN assets)
- Endpoints whose latency is dominated by a downstream the service doesn't control, served from the same worker pool as product endpoints
Does not apply when
- Server-originated events already sent from a background queue/thread (like analytics.capture) that never block the request
- The proxy is served by a separate process, edge/CDN rewrite or async server isolated from the API workers
Ways to fix
- Move the relay out of the Flask app: use an edge/CDN or Next.js rewrite, or a reverse-proxy rule that forwards /ingest to PostHog without touching Python workers
- If it must stay in-app, give it tight connect/read timeouts and streaming, and consider a separate worker pool or an async worker class
- For event POSTs, consider acknowledging immediately and forwarding via the existing background queue, accepting that the browser won't see PostHog's response
- Exclude /ingest/* from service-level latency SLOs and regression detection, or track it separately
It was right about the cause: the relay waited on PostHog inside the request, and nothing else had slowed down. We took the lesson's advice and removed the relay; browser analytics now go straight to PostHog.
Step 4
Every coding agent on the repo reads it
The lesson goes into the repo through a pull request: one line in .codela/lessons.md, imported from CLAUDE.md and AGENTS.md. Claude Code, Cursor and Codex load it at the start of every session. This is the repo's real index:
# Production lessons for this repo (from Codela) Each line is a rule learned from a real production incident in this repo. Follow it whenever your change touches what it describes, and open the lesson's file for the evidence and ways to fix it before changing that code. 1. [high] Claude often returns JSON inside ```json fences despite the prompt; strip fences (or use structured output) before json.loads on any model reply. 2. [high] Deploy-webhook analysis waits ~16 min; keep that wait in the SQLite deploy_jobs queue on the persistent volume, never in-process timers lost on redeploy. 3. [medium] /ingest/* relays browser analytics to PostHog in-request (p95 619ms); proxied third-party latency holds API workers and dominates service p95. 4. [medium] POST /lessons-to-repo/sync runs sync_repo (several GitHub calls) serially per linked repo in-request (1274ms); use background sync_async or parallelise with a time budget. 5. [medium] GitHub calls on page-load paths need short timeouts and caching; 403 vs 404 already tells no-permission from repo-not-visible, so skip the second probe. 6. [low] Sentry connector probes spans then transactions per query and only caches mode on data; empty results re-probe both forms every call.
The full lesson stays in its own file, opened only when an agent works on that code, so the index stays short.
Step 5
If a pull request repeats it, Codela says so
Example comment. The lesson is the real one above; the pull request is made up for illustration.
codela[bot] commented
Codela: production lessons
This PR looks like it repeats a mistake this repo already made in production.
/ingest/* relays browser analytics to PostHog in-request (p95 619ms); proxied third-party latency holds API workers and dominates service p95.
Where: app/main.py: a new /track route forwards each event to a third party before responding
Learned from: p95 latency 22ms → 520ms after deploy 077d769
Ways to fix: acknowledge immediately and forward from a background queue; or move the relay out of the API workers.
In our tests, agents without the lesson made the same mistake every time. With Codela's lesson, none of them did.
Needs GitHub and a monitoring tool (SigNoz, Datadog, Sentry, New Relic or Grafana). Free for one repo.