This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.
Project Overview
cloudflare/agentic-inbox is an edge-native, self-hosted autonomous AI email client. Built on Cloudflare Workers, React Router v7, and Workers AI (Llama 3.1), it automatically processes inbound mail routed through Cloudflare Email Routing.
Each mailbox operates inside an isolated Cloudflare Durable Object (MailboxDO) backed by an embedded SQLite database. The AI agent uses Model Context Protocol (MCP) tools to search mail history, retrieve context across threads, and draft responses autonomously.
While running local integration tests for the agentic inbox stack, automated thread retrieval would intermittently fail. When an incoming email contained a long subject header or a tracking string, the AI agent's background context search crashed the worker execution context entirely with a server error.
Bug Fix or Performance Improvement
I resolved Issue #36: "long search strings can 500 via Durable Object SQLite LIKE pattern limit".
Technical Breakdown: SQLite Pattern Bounds on Edge Runtimes
Cloudflare Workers Durable Objects embed a lightweight SQLite instance directly into the edge memory space. To prevent memory exhaustion and denial-of-service vectors on shared edge hardware, SQLite compile-time defaults enforce a strict 50-byte maximum length on SQL LIKE pattern strings (SQLITE_MAX_LIKE_PATTERN_LENGTH = 50).
In MailboxDO.#buildSearchConditions, user-supplied search parameters (query, from, to, and subject) were interpolated directly into wildcard patterns using %${param}%.
When executing a query like:
SELECT * FROM emails WHERE subject LIKE '%cf-agentic-inbox-external-inbound-test-20260728T203000Z%'
SQLite evaluates the byte length of the pattern parameter itself rather than the column string length. Because string formatting appends two % wildcard bytes around the input, any search parameter longer than 48 characters pushes the pattern buffer past 50 bytes.
For instance, a search term of 49 characters produced a 51-byte pattern (% + 49 chars + % = 51 bytes). When executed against the Durable Object's SQLite instance, SQLite threw an unhandled runtime error:
Uncaught Error: SQLite pattern length limit exceeded (pattern length exceeds 50 bytes in LIKE clause)
This error crashed the Durable Object execution context and returned an HTTP 500 Internal Server Error to the client.
Impact on the AI Agent
The AI agent relies on the searchEmails tool to retrieve past context when evaluating incoming threads. When an automated workflow or user prompt passed a subject line, message ID, or long search term exceeding 48 characters to searchEmails, the tool call failed with an HTTP 500. This unhandled crash broke the agent's autonomous execution loop mid-task, resulting in unhandled tool invocation failures.
Code
My Improvements
To resolve the crash without introducing extra runtime overhead or breaking search functionality:
- Defined
MAX_LIKE_PARAM_LENGTH = 48inworkers/durableObject/index.ts. - Sliced input strings to 48 characters before constructing the
%...%wildcard patterns acrossquery,from,to, andsubjectsearch fields. - Because 48 characters plus 2 wildcard bytes equals 50 bytes, the generated pattern string is guaranteed to stay within SQLite's 50-byte limit.
diff --git a/workers/durableObject/index.ts b/workers/durableObject/index.ts
index e2cbbfa..57cfce8 100644
--- a/workers/durableObject/index.ts
+++ b/workers/durableObject/index.ts
@@ -671,20 +671,22 @@ export class MailboxDO extends DurableObject<Env> {
return `?${paramIdx}`;
};
+ const MAX_LIKE_PARAM_LENGTH = 48;
if (query) {
- const p1 = addParam(`%${query}%`);
- const p2 = addParam(`%${query}%`);
- const p3 = addParam(`%${query}%`);
- const p4 = addParam(`%${query}%`);
+ const q = query.slice(0, MAX_LIKE_PARAM_LENGTH);
+ const p1 = addParam(`%${q}%`);
+ const p2 = addParam(`%${q}%`);
+ const p3 = addParam(`%${q}%`);
+ const p4 = addParam(`%${q}%`);
conditions.push(`(${prefix}subject LIKE${p1} OR ${prefix}body LIKE${p2} OR ${prefix}sender LIKE${p3} OR ${prefix}recipient LIKE${p4} OR ${prefix}cc LIKE${p4} OR ${prefix}bcc LIKE${p4})`);
}
if (folder) {
const p = addParam(folder);
conditions.push(`${prefix}folder_id = (SELECT id FROM folders WHERE name = ${p} OR id =${p} LIMIT 1)`);
}
- if (from) { const p = addParam(`%${from}%`); conditions.push(`${prefix}sender LIKE${p}`); }
- if (to) { const p = addParam(`%${to}%`); conditions.push(`(${prefix}recipient LIKE${p} OR ${prefix}cc LIKE${p} OR ${prefix}bcc LIKE${p})`); }
- if (subject) { const p = addParam(`%${subject}%`); conditions.push(`${prefix}subject LIKE${p}`); }
+ if (from) { const p = addParam(`%${from.slice(0, MAX_LIKE_PARAM_LENGTH)}%`); conditions.push(`${prefix}sender LIKE${p}`); }
+ if (to) { const p = addParam(`%${to.slice(0, MAX_LIKE_PARAM_LENGTH)}%`); conditions.push(`(${prefix}recipient LIKE${p} OR ${prefix}cc LIKE${p} OR ${prefix}bcc LIKE${p})`); }
+ if (subject) { const p = addParam(`%${subject.slice(0, MAX_LIKE_PARAM_LENGTH)}%`); conditions.push(`${prefix}subject LIKE${p}`); }
if (date_start) { const p = addParam(date_start); conditions.push(`${prefix}date >=${p}`); }
if (date_end) { const p = addParam(date_end); conditions.push(`${prefix}date <=${p}`); }
if (is_read !== undefined) { const p = addParam(is_read ? 1 : 0); conditions.push(`${prefix}read =${p}`); }
Architectural Trade-offs and Production Refinement
Truncating search strings to 48 characters is a surgical fix that respects edge database bounds while preserving search accuracy. In standard user email workflows, the first 48 characters of a sender address, recipient, or subject line contain more than enough entropy to return exact SQL matches.
By performing parameter slicing directly before SQL binding, we avoid mutating the original search query in the application state or breaking downstream UI components that display the full search string to the user.
During local iterations, adding external testing dependencies introduced unwanted modifications to package.json and lockfiles. To meet Cloudflare's contribution standards, I stripped out all external test dependencies and dev-environment flags. The final pull request delivers a 10-line, 1-file, 0-dependency fix that compiles cleanly via npm run typecheck and npm run build.
Best Use of Sentry
Sentry was instrumented at the Worker entry point using @sentry/cloudflare to capture tracing spans and uncaught exceptions across Cloudflare Workers and Durable Objects.
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
fetch: (request, env, ctx) => app.fetch(request, env, ctx),
}
);
1. Error Discovery and Exception Breadcrumbs
When the search endpoint was queried with a 49-character parameter (GET /api/v1/mailboxes/test-mailbox/search?subject=cf-agentic-inbox-external-inbound-test-20260728T203000Z), Sentry captured the unhandled exception in real time under issue AGENTIC-INBOX-1.
The trace preview highlighted the failed http.server root span and pinned the exact line in MailboxDO.#buildSearchConditions where SQLite threw the pattern length error.
2. Sentry Seer AI Root Cause Analysis
Sentry Seer RCA analyzed the stack trace, inspected the underlying Durable Object code, and diagnosed the issue:
Root Cause: Search query parameters are passed unsanitized into SQLite LIKE clauses, exceeding Cloudflare Workers' 50-byte LIKE pattern limit.
Plan: Truncate search filter parameters to 48 characters before wrapping them in LIKE patterns, keeping patterns within SQLite's 50-byte limit.
3. Automated Fix Generation via Sentry Bot
Seer generated a patch directly inside Sentry and automatically opened Pull Request #1 (Fix: Truncate search parameters for SQLite LIKE compatibility) on the repository fork via sentry bot.
4. Post-Fix Telemetry Verification
After applying the truncation patch, the same 49-character search request was re-executed.
Sentry's Traces view confirmed the resolution:
-
HTTP Status:
200 OK -
Sentry Status:
status: ok -
Uncaught Exceptions:
Issues: 0 -
Response Payload:
{"emails":[],"totalCount":0}
The trace timeline turned solid green, verifying that search operations now complete reliably without crashing the Durable Object context.
Debugging edge runtimes and Durable Objects can often feel like working inside a black box because errors occur deep within isolate bounds. Instrumenting @sentry/cloudflare allowed tracing the failure across the Worker-to-Durable-Object boundary instantly. Seeing Sentry Seer AI analyze the stack trace, identify SQLite's internal compile limit, and automatically author a working patch via sentry bot demonstrated how modern observability tools are moving from passive error logging to active, automated resolution.
Best Use of Google AI
Google AI (Gemini) served as an active AI pair programmer throughout the root-cause analysis, edge constraint verification, and surgical refactoring process.
1. Edge Runtime Constraint Verification
When Sentry caught the uncaught SQLite exception, Gemini helped analyze the edge runtime constraints by cross-referencing Cloudflare Durable Objects' compile-time SQLite flags against standard SQLite specifications. It confirmed that SQLITE_MAX_LIKE_PATTERN_LENGTH defaults to 50 bytes and verified that SQLite evaluates the byte length of the formatted pattern string (%param%) rather than the underlying column value length.
2. Surgical Boundary Calculation
Gemini assisted in evaluating string slicing thresholds (MAX_LIKE_PARAM_LENGTH = 48) to ensure that 48 characters plus 2 wildcard bytes (%...%) guaranteed 100% compliance with SQLite's 50-byte buffer limit across all query fields (query, from, to, subject) without introducing edge-case truncations on standard search strings.
3. Production Standard Code Audit
To ensure the pull request met Cloudflare's upstream contribution guidelines, Gemini was used to review the git diff, identifying and removing temporary development flags, mock files, and extra package dependencies. This resulted in a clean 10-line, 1-file, zero-dependency contribution ready for upstream review.







Top comments (1)
Had really enjoyable bug smashing and working through this with sentry.My experience was very enjoyable and sentry itself felt real intuitive to use even though it was my first interaction with it.Will definitely keep it in mind for the side projects i am building currently.