Describe the bug
Since #15991 (shipped in 2.65.0), calling a command() from server code that is not a /_app/remote/… request (for example a +server.ts route, or a cron endpoint) throws as soon as the command calls requested():
Error: requested(...) can only be called in the context of a command/form remote function
Up to 2.64.0 this worked. The behaviour change is not mentioned in the PR, the changeset (fix: dedupe remote data, patch) or the changelog, and it is inconsistent with query.refresh(), which silently no-ops in exactly the same situation.
What changed in #15991 (commit 372a6a69):
app/server/remote/command.js no longer does state.remote.refreshes ??= new Map() when the command function runs.
app/server/remote/requested.js replaced if (!store) throw … with if (!state.is_in_remote_form_or_command) throw ….
server/respond.js initialises is_in_remote_form_or_command: false for every request.
server/remote.js is the only place that sets it to true, inside the form / command branches of the remote-function HTTP handler and the no-JS form POST handler.
So "inside a command" used to mean "the command wrapper is running" and now means "the request came through the remote-function endpoint". A command invoked from any other server entry point reads as "not in a command". The comment above the check in requested.js still says the maps "will be initialized by the command/form wrapper", which is no longer what happens.
Why it's inconsistent: refresh() in app/server/remote/query.js handles the same case with
if (!event.isRemoteRequest) {
// or this is a no-JS form submission
return;
}
i.e. a no-op. requested() throws. If calling commands outside remote requests is unsupported, I'd expect both to throw (and the docs to say so). If it is supported, I'd expect requested() to no-op like refresh(), or the command() wrapper to set the flag as it used to set the map.
Our case: we expose selected commands over a public JSON RPC route (/api/rpc/[procedure]/+server.ts) that validates input and calls the same command() functions the UI uses. Every command that does requested(someQuery, 1).refreshAll() started returning 500 after upgrading 2.61 → 2.70. We've worked around it with a wrapper that no-ops when getRequestEvent().isRemoteRequest is false, mirroring refresh().
Suggested resolutions (any one would do):
requested() returns an empty result outside remote requests, matching refresh(); or
- the
command() / form() wrappers set is_in_remote_form_or_command themselves (restoring the pre-2.65 semantics); or
- document that commands must not be called from non-remote server code, and make
refresh() throw too so the rule is consistent.
Reproduction
Minimal, three files (I can push a StackBlitz/repo if that's preferred):
// src/lib/data.remote.ts
import { query, command, requested } from '$app/server';
export const getItems = query(async () => ['a', 'b']);
export const addItem = command(async () => {
// ... write something ...
await requested(getItems, 1).refreshAll();
return { ok: true };
});
// src/routes/api/add/+server.ts
import { json } from '@sveltejs/kit';
import { addItem } from '$lib/data.remote';
export const POST = async () => json(await addItem());
<!-- src/routes/+page.svelte -->
<script>
import { addItem } from '$lib/data.remote';
</script>
<button onclick={() => addItem()}>via remote endpoint (works)</button>
<button onclick={() => fetch('/api/add', { method: 'POST' })}>via +server.ts (500 since 2.67)</button>
- kit 2.64.0: both buttons succeed.
- kit ≥ 2.65.0: the second button returns 500 with the error above.
Logs
Error: requested(...) can only be called in the context of a command/form remote function
at requested (node_modules/@sveltejs/kit/src/runtime/app/server/remote/requested.js:131:9)
at addItem (src/lib/data.remote.ts)
at POST (src/routes/api/add/+server.ts)
System Info
Node: 24.13.0
@sveltejs/kit: 2.70.3 (bisected to 2.65.0 / #15991 by diffing the published tarballs 2.64.0 → 2.65.0)
svelte: 5.56.6
vite: 8.2.2
OS: macOS 25.6 (also reproduced on Linux CI)
Severity
annoyance (there is a workaround, but it's an unannounced behaviour change in a patch release)
Additional Information
Related: #16129 reworded the requested docs to "inside a remote command or form callback" shortly after, but a command called from +server.ts is inside the command callback, so the docs don't currently distinguish the two cases either.
Edit: corrected the version the change shipped in. The changelog lists #15991 under both 2.65.0 and 2.67.0; the published tarballs show the guard first appears in 2.65.0.
Describe the bug
Since #15991 (shipped in 2.65.0), calling a
command()from server code that is not a/_app/remote/…request (for example a+server.tsroute, or a cron endpoint) throws as soon as the command callsrequested():Up to 2.64.0 this worked. The behaviour change is not mentioned in the PR, the changeset (
fix: dedupe remote data, patch) or the changelog, and it is inconsistent withquery.refresh(), which silently no-ops in exactly the same situation.What changed in #15991 (commit 372a6a69):
app/server/remote/command.jsno longer doesstate.remote.refreshes ??= new Map()when the command function runs.app/server/remote/requested.jsreplacedif (!store) throw …withif (!state.is_in_remote_form_or_command) throw ….server/respond.jsinitialisesis_in_remote_form_or_command: falsefor every request.server/remote.jsis the only place that sets it totrue, inside theform/commandbranches of the remote-function HTTP handler and the no-JS form POST handler.So "inside a command" used to mean "the command wrapper is running" and now means "the request came through the remote-function endpoint". A command invoked from any other server entry point reads as "not in a command". The comment above the check in
requested.jsstill says the maps "will be initialized by the command/form wrapper", which is no longer what happens.Why it's inconsistent:
refresh()inapp/server/remote/query.jshandles the same case withi.e. a no-op.
requested()throws. If calling commands outside remote requests is unsupported, I'd expect both to throw (and the docs to say so). If it is supported, I'd expectrequested()to no-op likerefresh(), or thecommand()wrapper to set the flag as it used to set the map.Our case: we expose selected commands over a public JSON RPC route (
/api/rpc/[procedure]/+server.ts) that validates input and calls the samecommand()functions the UI uses. Every command that doesrequested(someQuery, 1).refreshAll()started returning 500 after upgrading 2.61 → 2.70. We've worked around it with a wrapper that no-ops whengetRequestEvent().isRemoteRequestis false, mirroringrefresh().Suggested resolutions (any one would do):
requested()returns an empty result outside remote requests, matchingrefresh(); orcommand()/form()wrappers setis_in_remote_form_or_commandthemselves (restoring the pre-2.65 semantics); orrefresh()throw too so the rule is consistent.Reproduction
Minimal, three files (I can push a StackBlitz/repo if that's preferred):
Logs
System Info
Severity
annoyance (there is a workaround, but it's an unannounced behaviour change in a patch release)
Additional Information
Related: #16129 reworded the
requesteddocs to "inside a remotecommandorformcallback" shortly after, but a command called from+server.tsis inside the command callback, so the docs don't currently distinguish the two cases either.Edit: corrected the version the change shipped in. The changelog lists #15991 under both 2.65.0 and 2.67.0; the published tarballs show the guard first appears in 2.65.0.