Skip to content

fix(pages): keep the named action key out of middleware redirects, rewrites and the page URL - #530

Merged
Brooooooklyn merged 8 commits into
mainfrom
fix/named-action-followups
Oct 21, 2026
Merged

Brooooooklyn merged 8 commits into
mainfrom
fix/named-action-followups

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Follow-ups to #522. That PR dropped the action key (?delete) only from c.redirect() inside the action. This covers the other places it leaked.

What changes

Case: POST /books?delete&page=2 Before After
Auth / user middleware c.redirect('/login') /login?delete&page=2 /login?page=2
Action c.rewrite('/api/x'), then c.redirect('/done') /done?delete&page=2 /done?page=2
c.redirect('/books-v2', 307) (or 308) /books-v2?page=2 (since #522) /books-v2?delete&page=2
PUT /books?update + c.redirect('/books') (302) /books (since #522) /books?update
API route under a catch-all named-action page: POST /api/x?return=/a + redirect ?return dropped (earlier commit here) ?return=/a kept
Action re-renders the page: page.url /books?delete&page=2 /books?page=2
…address bar after useForm / action() /books?delete&page=2 /books?page=2

The key goes only when the browser follows with a GET (303, or 301/302 after a POST). Other redirects send the same method again, and the destination needs the key to pick the action.

Redirect and rewrite rules (void.json routing) are unchanged: they keep the query as sent. On Void Cloud, platform dispatch applies them before the Worker and knows nothing about pages, so every target leaves them alone.

How

compile.ts      app.use(createNamedActionMarker(matchedRoutes))   ← after routing rules, before auth and user middleware
                matchedRoutes comes from the app's own resolved hono/route (Hono keys it by a per-copy Symbol)
                marks only if the first matched Void route handler (tagged: API, page GET, page mutation)
                is a named-action page; user middleware of any arity is skipped
routing.ts      c.redirect: drop the first query segment if marked and the browser follows with a GET
                c.rewrite: no new query → mark the new request too
protocol.ts     mutation page.url: drop the key if marked
client          after a POST that renders: if page.url's query == our URL minus its first segment,
                write that to history
pages/action-key.ts   shared WeakSet + string helper (no regex). Rolldown drops the WeakSet from client bundles.

Docs: the actions guide now says what happens to the query. The rewrites reference mentions middleware and 307/308.

Flaky tests (second commit)

Each of these failed in a local full run under load:

Test Cause Fix
plugin-class-a-wrapper … during build macOS reports server.mjs, written just before startup, as changed (2 of 6 runs in a probe). The reload gives the second import a new Counter. watch: null (12 of 12 runs stable)
plugin-vite-environment #472 test Real dev server: about 1 s alone, >5 s under load watch: null, lighter schema file, 30 s limit
plugin-entry-imports Imports the whole plugin entry: about 4.2 s alone 30 s limit

Tests

  • routing-runtime.test.ts: middleware, 307, rewrite, rewrite with its own query, and page URL. 5 rows fail on main.
  • pages-client.test.ts: history URL after a POST, 4 rows. 2 fail on main; the other 2 check that single-action URLs stay unchanged.
  • pages-compile.test.ts: the marker is emitted after routingMiddleware and before user middleware, only for actions.
  • vp run test --unit: all pass, except one cf-version-runner test that timed out under load and passes alone (not touched here). Lint, format and typecheck are clean.

🤖 Generated with Claude Code

Brooooooklyn and others added 2 commits October 21, 2026 16:43
…writes and the page URL

Follow-ups to #522, which dropped the action key only from c.redirect()
inside the action:

- Middleware: pages with named actions now mark the request before
  routing rules, auth and user middleware, so an auth redirect on
  POST /books?delete&page=2 goes to /login?page=2.
- Rewrites: a rewrite that keeps the query keeps the mark, so a handler
  reached from the action drops the key too.
- 307 and 308 keep the key: they send the same request again, and the
  destination needs it to pick the action.
- Page URL: when the action renders the page again, page.url leaves the
  key out, and the client writes that URL to history instead of
  /books?delete&page=2.
- Docs: the actions guide says what happens to the query.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- plugin-class-a-wrapper: macOS can report server.mjs, written just
  before the dev server starts, as changed. The reload gave the second
  import a new Counter. Turn the watcher off.
- plugin-vite-environment (#472): same watcher fix, a lighter schema
  file without drizzle's sqlite-core, and a 30s limit for the real dev
  server.
- plugin-entry-imports: importing the whole plugin entry takes about 4s
  alone, so give it a 30s limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T10:15:27.552749Z ff46d9f Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2609ff5663

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/routing.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-named-action-followups-void-cloud.voidzero-docs.workers.dev (commit ff46d9f)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://687699fa-void-cloud.voidzero-docs.workers.dev ff46d9f 2026-10-21T10:11:42.769Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://7c96f32d-void-cloud.voidzero-docs.workers.dev d2ce123 2026-10-21T09:24:18.716Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://48fa4371-void-cloud.voidzero-docs.workers.dev 6f0b571 2026-10-21T09:21:00.338Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://ee2eae5a-void-cloud.voidzero-docs.workers.dev cbdb8ee 2026-10-21T09:05:17.214Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://883d911f-void-cloud.voidzero-docs.workers.dev 7615acb 2026-10-21T08:56:15.353Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://0d8e59e6-void-cloud.voidzero-docs.workers.dev 31f61a5 2026-10-21T08:51:01.441Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://e64e9feb-void-cloud.voidzero-docs.workers.dev 2609ff5 2026-10-21T08:44:39.863Z Visit the dashboard ↗

The marker runs before routing rules, but a redirect rule merged the full
request query into its location before c.redirect() saw it, so
POST /books?delete&page=2 with a /books -> /login rule still went to
/login?delete&page=2. Use the same filtered query as c.redirect(): drop
the key for 301/302/303, keep it for 307/308.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31f61a5d22

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/router/compile.ts Outdated
…handling

Platform dispatch applies redirect and rewrite rules before the Worker
and knows nothing about pages, so on Void Cloud a rule redirect keeps the
whole query. Filtering the key in native rules (31f61a5) made native
and hosted deploys answer the same request differently.

Revert that change and run the named-action marker after the routing
rules again, so every target applies rules to the query as sent, as
docs/guide/edge/redirects.md describes. Middleware, actions and
c.rewrite() still drop the key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7615acb309

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/routing.ts Outdated
Comment thread packages/void/src/router/compile.ts Outdated
…key on repeated methods

- The marker is now one global middleware. It looks at the routes Hono
  matched and marks the request only when a page with named actions is
  the handler that will answer. A catch-all page pattern no longer drops
  the first query param from an API route's redirect.
- The key goes only when the browser follows with a GET: after a 303,
  or after a 301/302 to a POST. Fetch repeats PUT, PATCH and DELETE on a
  301/302, and the repeated request needs the key to pick the action.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbdb8ee463

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/routing-pages.ts Outdated
Brooooooklyn and others added 2 commits October 21, 2026 17:19
Hono's matchedRoutes() reads the request through a Symbol that each copy
of Hono defines for itself. In dev the runtime's bare 'hono/route' import
was a different copy from the app's Hono (imported by resolved path), so
the marker read undefined and every page request returned 500.

The generated app now imports matchedRoutes from the resolved
'hono/route' path, next to Hono, and passes it to
createNamedActionMarker(). The runtime no longer imports 'hono/route'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A one-argument user middleware such as
defineMiddleware(c => c.redirect('/maintenance')) looked like the
answering route, so the request was left unmarked and the redirect
carried the action key. Void now tags the handlers it registers for
routes (API, page GET and page mutation handlers), and the marker skips
everything else, whatever its arity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2ce1236c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/routing-pages.ts
…andlers

With a catch-all page that has named actions, the marker skipped the
untagged auth (/api/auth/*), internal (/__void/*) and WebSocket fallback
handlers and marked the request for the page, so a global middleware
redirect dropped the first query param of an auth callback. Every
terminal route the generated app registers is now tagged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff46d9f70f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/runtime/routing.ts
@Brooooooklyn
Brooooooklyn merged commit 9f67470 into main Oct 21, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/named-action-followups branch October 21, 2026 10:49
Brooooooklyn added a commit that referenced this pull request Oct 21, 2026
…he request (#533)

Follow-up to #530 (Codex finding on that PR, which got merged before
this fix landed).

## Problem

A redirect that sends the same request again keeps the action key
(307/308, or 301/302 after PUT/PATCH/DELETE). When the destination has
its own query, the merge put the key **after** the destination's params:

```
PUT /books?delete&page=2 → c.redirect('/books-v2?tab=details', 302)
→ /books-v2?tab=details&delete=&page=2      repeated PUT picks "tab" ✗
```

## Fix (only in the `c.redirect` wrapper)

| Destination | Result |
|---|---|
| `/books-v2?tab=details` (another page) |
`/books-v2?delete&tab=details&page=2` (key first, as sent) |
| `/books-v2?tab=1&delete=x` (sets the key too) |
`/books-v2?delete=x&tab=1&page=2` (its value wins, moves first) |
| `/books?archive` (same page, own first key) | `/books?archive=&page=2`
(that key picks the action) |
| `/books-v2?` (bare reset) | `/books-v2?delete` (rest dropped, key
kept) |
| `/books-v2` (no query) | `/books-v2?delete&page=2` (unchanged, byte
for byte) |
| `#fragment` | stays last |

Unchanged: GET follow-ups (key dropped), unmarked requests,
`mergeQueryIntoLocation`, redirect rules and platform dispatch.

Known limit: a redirect to a **different** page whose first query key is
one of that page's actions (`c.redirect('/other?archive', 307)`) still
puts the request's key first. The redirect code can't tell an action key
from a normal param on another page.

## Tests

`routing-runtime.test.ts`: 10 new rows in the named-action table (307
and PUT 302 with a destination query, a valued key, a fragment, a bare
`?`, the same key in the destination, a same-page action key). 9 fail on
main. The POST 302 row passes on main too: it checks the GET follow-up
stays unchanged. routing-runtime, pages-client, pages-compile and
compile tests: 205/205. Lint, format and typecheck are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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