Fix stale SvelteKit workflow queue triggers#1984
Conversation
🦋 Changeset detectedLatest commit: bf8dfaf The changes in this PR will be included in the next version bump. This PR includes changesets to release 18 packages
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 |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests▲ Vercel Production (1 failed)fastify (1 failed):
💻 Local Development (82 failed)nitro-stable (82 failed):
🌍 Community Worlds (83 failed)mongodb (11 failed):
redis (7 failed):
turso (65 failed):
Details by Category❌ ▲ Vercel Production
❌ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a deployment edge case in the SvelteKit Vercel adapter integration where workflow queue triggers could remain attached to shared/catchall function configs after the workflow-specific functions are copied out, resulting in unintended extra queue consumers.
Changes:
- Added logic to strip workflow-related queue triggers from the shared source function’s
.vc-config.jsonafter copying. - Refactored the copy logic to reuse computed function directories (
funcDir,sourceFuncDir) for clarity. - Added a patch changeset for
@workflow/sveltekit.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/sveltekit/src/index.ts | Adds stripWorkflowQueueTriggers() and applies it to the shared source function config to prevent stale queue subscriptions. |
| .changeset/stale-sveltekit-triggers.md | Declares a patch release for @workflow/sveltekit documenting the fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const WORKFLOW_QUEUE_TOPICS = new Set(['__wkf_workflow_*', '__wkf_step_*']); | ||
|
|
There was a problem hiding this comment.
Done in bf8dfaf: the SvelteKit helper now reuses STEP_QUEUE_TRIGGER / WORKFLOW_QUEUE_TRIGGER from @workflow/builders instead of duplicating the topic strings.
TooTallNate
left a comment
There was a problem hiding this comment.
Approve
Clean fix for a real deployment bug. The root cause is exactly as described: SvelteKit's Vercel adapter writes a shared catchall function config (__data.json.func/.vc-config.json) that includes the workflow queue triggers, and the existing post-build hook copies it to dedicated flow.func/step.func outputs without ever clearing the source. Result: three (or more) deployment functions consuming the same queue.
What I verified
Idempotency of the strip helper across the two-iteration loop: First iteration (flow.func) reads source contents, copies to dest, overwrites dest config with the workflow-specific trigger, then strips workflow topics from source — both __wkf_workflow_* and __wkf_step_* are removed in one shot because WORKFLOW_QUEUE_TOPICS contains both. Second iteration (step.func) sees source with no remaining workflow triggers — filteredTriggers.length === experimentalTriggers.length short-circuits, no extra write. Final result: each dedicated func subscribes to exactly one workflow topic, source has none. ✅
Defensive layering in stripWorkflowQueueTriggers: missing file → return, non-array experimentalTriggers → return, no matching triggers → return without write, all-workflow → delete the property rather than leaving an empty array. Pure JSON in/out. Good hygiene.
New funcDir existence guard (the if (!fs.existsSync(funcDir)) continue; at the top of the loop) is a strict-mode improvement orthogonal to the queue-trigger fix — before this PR, fs.readdirSync(path.dirname(file)) would throw if SvelteKit didn't create the dest dir. Worth keeping.
Build + typecheck clean locally (pnpm turbo build --filter @workflow/sveltekit, pnpm typecheck). CI is green: 65 successes, 0 failures, 5 still in progress.
Three non-blocking observations — see inline
- Forward-port to
main— same bug exists there (V2 only hasflow.func, but the source is still shared). Per AGENTS.md the normal flow is main→stable via backport action. - Optional unit tests for
stripWorkflowQueueTriggers— the function is unusually testable (pure JSON-file transform) and the package currently has zero tests. - Changeset wording — accurate but terse; would help users recognize the symptom in their deployment.
None of these are blockers. Approving.
|
|
||
| import { SvelteKitBuilder } from './builder.js'; | ||
|
|
||
| const WORKFLOW_QUEUE_TOPICS = new Set(['__wkf_workflow_*', '__wkf_step_*']); |
There was a problem hiding this comment.
Heads-up: this PR targets stable directly, but the same bug exists on main too — V2 there only writes flow.func (no step.func), but __data.json.func source can still retain the __wkf_workflow_* trigger after the copy step. Per AGENTS.md the normal flow is main→stable via the backport action.
Worth opening a companion PR against main so this fix doesn't regress on the next stable release. The WORKFLOW_QUEUE_TOPICS set there is just ['__wkf_workflow_*'] (no step), but the helper + call site are otherwise identical.
Non-blocking for this PR — fine to land first and forward-port separately.
There was a problem hiding this comment.
Good call. I’m keeping this PR scoped to stable / 4.2.x because that was the requested release target, but main needs the same treatment as a separate follow-up PR.
| // a race to be created before svelte discovers entries | ||
| await builder.build(); | ||
|
|
||
| function stripWorkflowQueueTriggers(file: string) { |
There was a problem hiding this comment.
Optional: this is one of the most testable functions in the package — a pure transform over .vc-config.json contents. The package has zero tests today, so adding the first one would establish the pattern. Something like:
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import fs from 'fs-extra';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
// (export stripWorkflowQueueTriggers for testing, or pull it into a helpers module)
describe('stripWorkflowQueueTriggers', () => {
let dir: string;
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'wf-sveltekit-')); });
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
it('removes workflow topics, keeps others', () => {
const file = join(dir, '.vc-config.json');
fs.writeJsonSync(file, {
runtime: 'nodejs22.x',
experimentalTriggers: [
{ type: 'queue/v2beta', topic: '__wkf_workflow_*' },
{ type: 'queue/v2beta', topic: '__wkf_step_*' },
{ type: 'queue/v2beta', topic: 'app_user_signup' },
],
});
stripWorkflowQueueTriggers(file);
expect(fs.readJsonSync(file).experimentalTriggers).toEqual([
{ type: 'queue/v2beta', topic: 'app_user_signup' },
]);
});
it('deletes the array when all triggers were workflow', () => { ... });
it('no-ops when file is missing', () => { ... });
it('no-ops when experimentalTriggers is missing', () => { ... });
it('no-ops when no triggers matched (idempotent)', () => { ... });
});Would have caught the second-iteration idempotency I traced through during review (both topics are stripped on iter 1; iter 2 should no-op). Non-blocking.
There was a problem hiding this comment.
Done in bf8dfaf: I extracted the pure config transform into vc-config.ts and added focused coverage for preserving unrelated triggers, deleting empty trigger arrays, and leaving unrelated configs unchanged.
| '@workflow/sveltekit': patch | ||
| --- | ||
|
|
||
| Remove stale workflow queue triggers from shared SvelteKit Vercel function configs. |
There was a problem hiding this comment.
Wording nit: the changeset description is accurate but doesn't convey end-user impact. From a SvelteKit user's perspective the symptom they'll recognize in their changelog is duplicate / extra Workflow queue consumers in their deployment. Consider:
| Remove stale workflow queue triggers from shared SvelteKit Vercel function configs. | |
| --- | |
| '@workflow/sveltekit': patch | |
| --- | |
| Fix duplicate Workflow queue consumers in deployed SvelteKit apps by stripping stale `__wkf_workflow_*` and `__wkf_step_*` triggers from the shared source function after copying them to the dedicated `flow.func` / `step.func` outputs. |
Non-blocking.
There was a problem hiding this comment.
Done in bf8dfaf: updated the changeset to call out the duplicate Workflow queue consumers symptom for SvelteKit deployments.
|
Forward-port to |
Summary
flow.funcandstep.funcoutputs.@workflow/sveltekit.Root cause
SvelteKit adapter output can use shared
__data.json.func/catchall function configs. The Workflow SvelteKit integration copies those shared functions into dedicated workflow functions and patches queue triggers there, but stale queue triggers can remain on the shared source config. That can leave extra Workflow queue consumers in a deployment.Validation
pnpm --filter @workflow/sveltekit buildpnpm exec biome check packages/sveltekit/src/index.ts.well-known/workflow/v1/flow.funcfor__wkf_workflow_*.well-known/workflow/v1/step.funcfor__wkf_step_*Note:
pnpm changeset status --since=origin/stablereported the expected patch bump set after staging the changeset.