Skip to content

Route /api/* through the Worker so subscribe actually runs - #125

Merged
kixelated merged 1 commit into
mainfrom
fix/api-run-worker-first
Aug 20, 2026
Merged

Route /api/* through the Worker so subscribe actually runs#125
kixelated merged 1 commit into
mainfrom
fix/api-run-worker-first

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

What

One line in wrangler.jsonc: run_worker_first now lists /api/* alongside /.

Why

just deploy live failed its announce step with a Resend 422: "The audience you are sending has no contacts." The audience is empty because the subscribe form has never worked.

run_worker_first only listed "/", so for every other path Cloudflare's static asset server answers before the Worker runs. /api/subscribe isn't an asset, and the asset server rejects a POST to a non-asset path with a bare 405 — the Worker's handleSubscribe never executes.

Confirmed against production:

  • POST https://moq.dev/api/subscribe405 with an empty body. The Worker's own 405 branch returns the string Method Not Allowed, so this response isn't coming from our code.
  • GET https://moq.dev/api/subscribe → the asset 404 page.
  • new.moq.dev behaves the same way.

So every visitor who submitted the form got "Something went wrong. Try again?" and their address was silently dropped.

The rest of the chain is healthy and needed no changes: RESEND_API_KEY and RESEND_SEGMENT_ID are both set as secrets on the live Worker, and the handler's POST /contacts with a segments: [{ id }] array matches Resend's current API reference.

This is the same footgun CLAUDE.md already documents for moq.pub / moq.watch, where assets.run_worker_first is called out as load-bearing — it just bit the moq.dev Worker on a different path.

Verifying after deploy

curl -i -X POST https://new.moq.dev/api/subscribe \
  -H 'Content-Type: application/json' -d '{"email":"you@example.com"}'

Should return 200 {"ok":true} and the address should appear in the Resend "Blog" segment. Before this change it's an empty 405.

Not fixed here

  • Five orphaned Untitled draft broadcasts are sitting in Resend from the deploy retries (15:35–16:06 UTC). Each was created fine and then failed on send. Worth cleaning up in the dashboard.
  • The Open For Business announce won't fire on its own anymore — the announce snapshot expires an hour after the first attempt, after which a rerun reads the post as already live. Once the audience has real contacts, that one needs sending by hand.

(written by Fable 5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01RibbEtTdquFTTLJNEvrAn9

`run_worker_first` only listed "/", so Cloudflare's asset server answered
/api/subscribe before the Worker ever ran and rejected the POST with a bare
405. The subscribe form has been broken since it shipped: every signup was
dropped and the visitor saw a generic error, which is why the Resend "Blog"
audience is empty and `just deploy live` fails to announce.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RibbEtTdquFTTLJNEvrAn9
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe1e391d-371a-4d04-b797-5cf82e18567f

📥 Commits

Reviewing files that changed from the base of the PR and between 9c07d63 and dfffd46.

📒 Files selected for processing (1)
  • wrangler.jsonc

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The asset configuration now routes / and /api/* through the Worker before static asset handling. Other paths continue to route directly to static assets. The routing comment documents that API POST requests must avoid asset-level 405 responses.

Merge Risk: ⚪ Minimal · up to dfffd

This localized routing configuration change enables API requests to reach the Worker so subscriptions can run; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: routing /api/* through the Worker so the subscribe handler runs.
Description check ✅ Passed The description directly explains the configuration change, its cause, its impact on subscriptions, and verification steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/api-run-worker-first

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kixelated
kixelated merged commit 8d1f70a into main Aug 20, 2026
1 check passed
@kixelated
kixelated deleted the fix/api-run-worker-first branch August 20, 2026 16:52
kixelated added a commit that referenced this pull request Aug 20, 2026
`go get` resolves an import path by fetching it over HTTPS with ?go-get=1 and
reading the repo out of a <meta name="go-import"> tag, so serving that tag here
makes moq.dev own the published import path instead of whichever mirror repo
the code lands in. The Go wrapper can then be imported as `moq.dev/moq` rather
than `github.com/moq-dev/moq-go/moq`, which also means the identifier is `moq`
with no alias, and a future mirror rename costs a line in the table rather than
a breaking change for every consumer.

Two paths are served, both inert until the matching go.mod switches its module
path:

  moq.dev/moq      -> github.com/moq-dev/moq-go
  moq.dev/moq-ffi  -> github.com/moq-dev/moq-go-ffi

A module owns every path beneath it, because the go command asks about the full
import path (package directory included) and the tag has to answer with the
module path regardless. Requests without ?go-get=1 are people, so they redirect
to pkg.go.dev.

`run_worker_first` needs the /moq* wildcard for the same reason #125 needed
/api/*: an asset miss is answered with the 404 page rather than falling through,
so the Worker would never run. Verified against `wrangler dev`, which reproduces
that routing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Aug 20, 2026
`go get` resolves an import path by fetching it over HTTPS with ?go-get=1 and
reading the repo out of a <meta name="go-import"> tag, so serving that tag here
makes moq.dev own the published import path instead of whichever mirror repo the
code lands in:

  moq.dev/moq      -> github.com/moq-dev/moq-go
  moq.dev/moq-ffi  -> github.com/moq-dev/moq-go-ffi

Both are inert until the matching go.mod switches its module path, which is the
correct order: the tags have to be live before a mirror publishes a moq.dev/...
go.mod, or the first tag that lands resolves to nothing.

A module owns every path beneath it, because the go command asks about the full
import path while the tag has to answer with the module path regardless.
Requests without ?go-get=1 are people, so they redirect to pkg.go.dev, and the
de.moq.dev rewrite runs first so that host keeps its own namespace.

run_worker_first needs the /moq* wildcard for the same reason #125 needed
/api/*: an asset miss is answered with the 404 page rather than falling through,
so the Worker would never run. CI also runs `bun test` now, which it never did,
leaving every test in the repo unenforced.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant