Skip to content

fix(cloudflare): resolve AI Search bindings per request - #2404

Open
ttmx wants to merge 2 commits into
emdash-cms:mainfrom
ttmx:fix/ai-search-request-scope
Open

fix(cloudflare): resolve AI Search bindings per request#2404
ttmx wants to merge 2 commits into
emdash-cms:mainfrom
ttmx:fix/ai-search-request-scope

Conversation

@ttmx

@ttmx ttmx commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes AI Search operations across consecutive Worker requests by resolving request-scoped Cloudflare bindings each time. It removes isolate-level caching of the cloudflare:workers module and removes complexity.
Also removes the unneeded waitUntil, since EmDash already does this for the hooks.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5.6 via Opencode

Screenshots / test output

Not applicable; this is a non-visual runtime fix.

Copilot AI lite review requested due to automatic review settings August 10, 2026 09:32
@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/cloudflare size/M labels Aug 10, 2026
@changeset-bot

changeset-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1f874f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/cloudflare Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
emdash Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2404

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2404

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2404

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2404

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2404

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2404

emdash

npm i https://pkg.pr.new/emdash@2404

create-emdash

npm i https://pkg.pr.new/create-emdash@2404

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2404

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2404

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2404

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2404

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2404

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2404

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2404

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2404

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2404

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2404

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2404

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2404

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2404

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2404

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2404

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2404

commit: 4ee8d7f

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is correct. Caching the cloudflare:workers module import at isolate scope is unsafe because that module is request-scoped on Workers; the resolved env object can leak across requests. Removing the global Symbol.for(...) cache so the plugin re-imports it per call fixes that. The removed waitUntil wrapper is also redundant: I traced the hook dispatch in packages/core/src/emdash-runtime.ts and confirmed that content hooks are invoked inside after() (packages/core/src/after.ts), which already hands the promise to the adapter's waitUntil on Cloudflare. So indexing work remains background-extended without the plugin duplicating the effort.

The diff is clean and focused, the changeset uses the right package and describes the user-visible effect, and no stale exports or helpers are left behind.

The one issue is the new tests. They assert that info() and namespace.get() are called twice across two operations, but that's also true of the pre-fix code — the old code cached the module import, not the binding or instance. Because the Vitest mock returns a static fakeEnv, a reintroduced module-level import cache would still pass these tests. A regression test for request-scoped bindings should make import("cloudflare:workers") return distinct env snapshots per call and assert the second operation reads from the second snapshot.

Comment thread packages/cloudflare/tests/plugins/ai-search-indexing.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes AI Search behavior in the Cloudflare Worker runtime by ensuring Cloudflare bindings are resolved fresh (rather than being cached at the isolate level) and by relying on EmDash’s existing deferred-hook lifetime management instead of duplicating waitUntil usage inside the plugin.

Changes:

  • Removed isolate-level caching for import("cloudflare:workers") in the AI Search plugin so bindings can be resolved per operation/request.
  • Removed the plugin’s internal waitUntil anchoring for index sync operations (EmDash core already defers/anchors content hooks via after()).
  • Added tests and a changeset documenting the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/cloudflare/src/plugins/ai-search.ts Drops isolate-level caching of cloudflare:workers and removes redundant waitUntil anchoring for content hook index sync.
packages/cloudflare/tests/plugins/ai-search-indexing.test.ts Adds assertions intended to verify fresh binding-backed instance resolution across operations/route checks.
.changeset/fresh-ai-search-request-bindings.md Patch changeset for @emdash-cms/cloudflare describing the binding-resolution fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/cloudflare/tests/plugins/ai-search-indexing.test.ts Outdated
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Aug 10, 2026
@ttmx
ttmx force-pushed the fix/ai-search-request-scope branch from 7b6d09d to 4ee8d7f Compare August 10, 2026 14:13
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 10, 2026
@MA2153 MA2153 added the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is still correct: resolving cloudflare:workers per request instead of caching the module import at isolate scope is the right fix for request-scoped binding leakage, and the waitUntil wrapper is indeed redundant because the runtime already defers content hooks through after().

I re-checked the new regression test against the prior review feedback and it now validates the actual failure mode: vi.doMock swaps the env snapshot between the two simulated requests, and the second indexing operation is asserted to write into the second request's binding. If the old isolate-level module cache were reintroduced, the second operation would reuse the first request's env and the assertion would fail.

I also verified:

  • No stale references to WORKERS_MODULE_KEY, cfWaitUntil, or waitForSync remain.
  • All content hooks in packages/core/src/emdash-runtime.ts are still dispatched inside after(), so index/deindex work keeps being handed to the host's lifetime extender without the plugin duplicating it.
  • The changeset is still package-appropriate and user-facing.

No blocking issues. The previous needs_fixing test concern is resolved; the rest of the diff is clean.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 11, 2026

@khoinguyenpham04 khoinguyenpham04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls replace this with a workerd test using withEnv() and the same plugin/module instance across two calls. It must fail on main and pass here. Also, do avoid vi.doMock(), since that doesn’t match Workers behavior.

@github-actions github-actions Bot added needs-rebase size/L review/needs-rereview Author pushed changes since the last review and removed size/M review/approved Approved; no new commits since labels Aug 11, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 11, 2026
@ttmx
ttmx force-pushed the fix/ai-search-request-scope branch from e8fbe96 to 1f874f4 Compare August 11, 2026 15:12
@github-actions github-actions Bot added size/XL and removed size/L labels Aug 11, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 11, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 13, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 13, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is still correct: resolving cloudflare:workers per request instead of caching the module import at isolate scope fixes request-scoped binding leakage, and removing the plugin-level waitUntil wrapper is safe because EmDash's runtime already defers content hooks through after().

I re-checked the PR head statically and confirmed:

  • No stale references to WORKERS_MODULE_KEY, cfWaitUntil, waitForSync, loadWorkersModule, or getCloudflareEnv remain in packages/cloudflare.
  • env is imported directly from "cloudflare:workers" per AGENTS.md convention, and binding resolution is now synchronous and request-scoped.
  • packages/core/src/emdash-runtime.ts still dispatches content:afterSave, content:afterDelete, and the publish/schedule/restore hooks inside after(), so lifetime extension happens via the runtime rather than the plugin duplicating it.
  • The new packages/cloudflare/tests/workerd/ai-search-request-scope.test.ts exercises the actual regression: it creates one plugin instance, swaps the env snapshot across two withEnv blocks, and asserts each simulated request writes to its own binding. If the old isolate-level module cache were reintroduced, the second request would reuse the first request's env and the assertion would fail.
  • The changeset targets the right package with a user-facing description, and @cloudflare/vitest-pool-workers is appropriately added to devDependencies with the catalog version bump.

No blocking issues. The prior needs_fixing test concern is resolved and the rest of the diff remains clean.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants