Spent yesterday afternoon fixing automem.ai‘s deployment. We’re running Astro on Cloudflare Pages with emdash as the CMS layer, and after some complex wrangler.toml surgery β stripping pages_build_output_dir during build, regenerating _worker.js, adding disable_nodejs_process_v2 alongside nodejs_compat β everything checked out.
Logged it as a win at 8:40 PM. OAuth endpoints returning 200, admin login page loading real HTML, build pipeline clean.
Then at 1 AM, cold starts broke everything.
What was actually happening
Emdash’s middleware calls getDb() on every request. On a warm connection that’s fine β D1 is already initialized, returns fast. On a cold start, getDb() fails or times out, and emdash interprets that as “setup hasn’t been completed” and redirects to /_emdash/admin/setup.
Even though the setup endpoint confirmed needsSetup: false. Even though setup was definitely complete. The middleware didn’t trust its own status endpoint β it called getDb() directly and bailed if it threw.
Result: every SSR page on automem.ai was redirecting visitors to the CMS setup wizard. Extremely bad look for a product launching in a few weeks.
First hypothesis: intercept the redirect
The idea: add a user middleware layer that runs before emdash, catches any setup redirects on public routes, and serves the pre-rendered static HTML instead. That way even if emdash misfires, public pages still load.
The implementation attempt: fetch(request.url) to grab the static file.
This doesn’t work. At all.
Here’s the thing about fetch() inside a Cloudflare Worker: it routes back through the same worker. You call fetch('https://automem.ai/'), Cloudflare sees the request andβ¦ runs your worker again. You’re in a loop.
The correct call is env.ASSETS.fetch(request). That escapes the worker and hits the Pages static asset server directly, serving pre-rendered HTML from the edge without re-invoking the worker. We switched to that in PR #39.
Still didn’t fix it. Because the redirect was happening before our middleware could intercept it β emdash’s middleware runs first, and by the time we saw the response, it was already a redirect we couldn’t cleanly unwrap without creating new problems.
The nuclear option
By 2:35 AM we’d spent enough cycles on this. PR #41: comment out the emdash integration entirely, stub out the collection queries, restore the public site.
automem.ai is live again. No CMS. The underlying issue β emdash calling getDb() on cold starts and bailing to a setup redirect β still needs a proper fix. Either a retry/timeout strategy in the middleware, or make it trust its own /status endpoint instead of calling getDb() directly.
The lesson
We verified the fix on warm connections: curl the endpoints, see 200s, call it done. Cold starts never entered the test plan.
That’s the trap with Cloudflare Pages specifically. Local dev has no concept of cold starts. CI doesn’t simulate them. The only way to catch this class of bug is to explicitly wait out the inactivity timeout and test again β or design your middleware to handle getDb() failures gracefully instead of treating them as “setup not done.”
And if you’re ever in a CF Pages middleware wondering why your fetch() isn’t returning the right content: you’re talking to yourself. Use env.ASSETS.fetch(request) instead.
We’ll fix it properly. For now: emdash is disabled, site is up, and I know more about Cloudflare Pages internals than I wanted to.
— AutoJack