Skip to content
imarch.dev
Back to blog
· 5 min read

Four Bugs in One Evening

ai debugging claude code продукт

That morning I shipped two new tools for the bot - lead capture and CV delivery. Everything worked. By noon I started testing for real and hit four bugs that nested inside each other like matryoshka dolls.

Four bugs in one evening

Bug chain (each one amplified the next)
Lost session
Language switch
Email hallucination
Landed in spam

Bug 1: Lost sessions

A visitor asks for a CV. The bot asks for their email. The visitor types it in. The bot responds as if they’ve never met.

The culprit: two uvicorn workers. Each one stores sessions in its own memory. The first request lands on worker 1, the second on worker 2. Worker 2 knows nothing about that session and creates a fresh one.

Before: 2 workers
Request 1 → Worker 1 (session A)
Request 2 → Worker 2 (session ???)
Context lost
After: 1 worker
Request 1 → Worker 1 (session A)
Request 2 → Worker 1 (session A)
Context preserved

Multiple workers make sense for CPU-bound workloads. A chatbot is pure I/O: waiting on the Claude API, waiting on SMTP. A single async worker handles twenty concurrent connections without breaking a sweat. In my case, two workers didn’t double the throughput - they doubled the bugs.

The fix was one digit in the Dockerfile. --workers 2 became --workers 1.

Bug 2: An email address changes the language

The visitor writes in Russian. The bot replies in Russian. The visitor types ilikosha@icloud.com. The bot switches to English.

The language detector counts Latin vs. Cyrillic characters in the message. An email address is pure Latin. Zero Cyrillic. The detector concludes it’s English and tags the message [Reply in English].

The fix: strip email addresses, URLs, and @handles from the text before counting. Those aren’t natural language - they shouldn’t factor in.

Bug 3: Hallucinated email

Visitor: “send me the resume.” Instead of asking for an email, the bot grabs info@imarch.dev from the system prompt and fires the tool. The email lands in my own inbox.

Haiku 3 is a small model. When the tool definition says visitor_email is required but there’s no email in the conversation, the model won’t stop and ask. It’ll grab the first plausible address from context. And the system prompt has three of them.

Three layers of defense:

1. Tool description forbids addresses from the prompt
2. System prompt: ask for email first
3. Server-side guard: email in chat history? No → reject

No single layer is enough. Haiku finds a way around each one individually. All three together hold. This is defense in depth - the same principle as network security: no single barrier is perfect, but together they cover each other’s blind spots.

Bug 4: Emails landing in spam

Lead notifications arrive fine. CV emails go straight to spam. Across multiple inboxes.

The difference: lead notifications go to my own domain (Cloudflare Email Routing), while CV emails go to external inboxes (iCloud, Gmail). External servers are stricter.

The email itself looked spammy. noreply@ as sender. An HTML template with a big button. Minimal text. A generic subject line “Ilyas Mustafin - CV.” No recipient name, no way to reply.

The fix was making it look like a normal email from a real person:

Before (spam)
From: noreply@imarch.dev
Subject: Ilyas Mustafin - CV
Body: HTML + big button
Reply-To: none
After (inbox)
From: Ilyas Mustafin <info@...>
Subject: CV as requested
Body: plain text + signature
Reply-To: real mailbox

I also added Resend (Amazon SES) to the domain’s SPF record to authorize the sending server. Without SPF and DKIM, even a perfectly written email from a new domain will land in spam - mail servers trust signatures, not content.

Takeaway

Four bugs. None of them were hard on their own. But they stacked: a lost session broke the guard, a broken guard let the hallucination through, and the hallucinated email flew straight to spam. A classic cascading failure - each bug masked the previous one, making it impossible to find the root cause in a single test run.

The most useful fix of the four was actually a bonus - an admin endpoint for clearing the cache. Before, I had to SSH into the server. Now it’s one curl with a token. Small thing, but when you’re tweaking the prompt five times in one evening, it saves your sanity.

The main lesson of the evening: an AI chatbot is not “plug in an API and forget.” It’s a system with state, language logic, a model that hallucinates, and external services that filter the output. Every layer can break, and it breaks silently - you only find out when a user says “the bot is acting weird.”


The bot is live. Try asking for a CV or submitting a request - the chat button is in the bottom-right corner. Or reach out directly if something’s still broken.

Share:

Related posts