fix(triggers): stop workflow copies reusing or wiping a block's webhook identity - #6090
fix(triggers): stop workflow copies reusing or wiping a block's webhook identity#6090waleedlatif1 wants to merge 2 commits into
Conversation
…s webhook URL Pasting or duplicating a deployed webhook trigger block carried the source's triggerPath and webhookId onto the clone. The clone rendered the original's public URL, and deploy resolved both blocks to one path — landing on the path_deployment_unique index as an opaque 500 rather than an actionable error. PR #1784 fixed this for duplicate, but the guard was deleted by three later paste refactors (#2649, #2738, #3024) and had no regression test. - Clear only the webhook identity (webhookId, triggerPath) on clones, so deploy falls back to the clone's freshly generated block id for the path. Trigger configuration (triggerConfig, triggerId) is preserved — it can be a legacy trigger's only config home. - Clear both the subBlocks structure and the sub-block value map: the value map is authoritative in mergeSubblockStateWithValues. - Gate on isBlockActingAsTrigger, not the subblock id: Discord, Attio, and Vercel action blocks expose a required user-entered webhookId field. Mirrors deploy's resolveTriggerId so both sides agree on which blocks own a webhook. - Cover duplicateBlock, which bypasses regenerateBlockIds entirely. - Reject two same-workflow trigger blocks sharing a path at deploy with a 400 naming both blocks; the existing guards are cross-workflow only. Import/export and server-side duplicate paths are deliberately untouched.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Cloned trigger blocks (paste, duplicate, Workflow-level copies (duplicate, fork, import/export) now gate runtime stripping on Deploy validation rejects two enabled triggers in one workflow sharing the same explicit Reviewed by Cursor Bugbot for commit 791cede. Configure here. |
Greptile SummaryFixes webhook identity handling on workflow copy/duplicate/import so trigger clones drop webhookId/triggerPath while non-trigger action blocks keep user-entered webhookId, and adds a same-workflow duplicate triggerPath deploy guard.
Confidence Score: 3/5Not fully safe to merge until the same-workflow deploy path guard normalizes triggerPath the same way claimWebhookPath does, so slash/whitespace variants get the actionable 400 instead of a later collision. The prior deploy-guard finding remains: duplicate paths that differ only by leading/trailing slashes or whitespace still bypass validateTriggerWebhookConfigForDeploy's map and can hit the unique index path without naming both blocks. Clone and non-trigger webhookId preservation changes look consistent with the intended gates and tests. Files Needing Attention: apps/sim/lib/webhooks/deploy.ts
|
| Filename | Overview |
|---|---|
| apps/sim/lib/webhooks/deploy.ts | Adds same-workflow duplicate triggerPath guard still keyed on raw path strings without claim-path normalization. |
| apps/sim/stores/workflows/utils.ts | Adds stripWebhookIdentityForClone and gates import/export runtime clear on isBlockActingAsTrigger. |
| apps/sim/lib/workflows/persistence/remap-internal-ids.ts | Requires isActingAsTrigger before stripping trigger runtime subblock ids on duplicate. |
| apps/sim/lib/workflows/triggers/trigger-utils.ts | Adds isBlockActingAsTrigger aligned with deploy resolveTriggerId (triggerMode or category triggers). |
| apps/sim/stores/workflows/workflow/store.ts | duplicateBlock now strips webhook identity on the clone without touching the source. |
| apps/sim/lib/workflows/persistence/duplicate.ts | Passes isBlockActingAsTrigger into sanitizeSubBlocksForDuplicate during workflow duplicate. |
| apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts | Fork/copy path passes isBlockActingAsTrigger into sanitize so action webhookId is kept. |
Reviews (2): Last reviewed commit: "fix(triggers): stop workflow copy paths ..." | Re-trigger Greptile
The trigger-runtime strip on the workflow-level copy paths was keyed on subblock id alone. Discord, Attio, and Vercel action blocks expose a REQUIRED user-entered `webhookId` field, so duplicating, forking, or importing a workflow blanked it while leaving the `webhookToken` it pairs with — a silently half-configured copy that fails at runtime with an error pointing nowhere near the copy that caused it. - Gate the TRIGGER_RUNTIME_SUBBLOCK_IDS strip on isBlockActingAsTrigger in sanitizeSubBlocksForDuplicate (server duplicate + workspace forking) and in updateBlockReferences (export/import). - Make sanitizeSubBlocksForDuplicate's flag a required parameter so every call site must decide rather than inheriting a silent default. - Widen isBlockActingAsTrigger to accept a DB block row as well as BlockState. Trigger blocks are unaffected — they still lose their webhook identity on a copy. Display-only system subblocks are still stripped unconditionally; those ids exist only on trigger definitions. Verified `scheduleInfo` is the only other collision and it belongs to the schedule block, which is category: 'triggers'.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 791cede. Configure here.
|
Closing in favor of a clean, minimal reimplementation. The diagnosis here was right and is now pinned down precisely: before deploy the webhook URL is derived ( But this PR accumulated scope that does not belong with that one-line-shaped fix, and an independent audit found the tests were passing for the wrong reason — every "clears" assertion rode the Also dropped as unjustified by the data: the same-workflow deploy guard (production has 0 live duplicate webhook paths, and it introduced a false positive for two Reimplementing off |
Summary
Two related bugs, same root cause: clearing trigger runtime subblocks by id alone, without checking whether the block actually owns a webhook.
1. Clone paths didn't clear the webhook identity (the reported bug)
triggerPathandwebhookIdonto the clone. The clone rendered the original's public URL, and deploy resolved both blocks to one path — hitting thepath_deployment_uniqueindex as an opaque 500 instead of an actionable error. Blocks templating any workflow that needs more than one webhook.webhookId,triggerPath) on clones, so deploy falls back to the clone's freshly generated block id. Trigger configuration (triggerConfig,triggerId) is preserved —triggerConfigcan be a legacy trigger's only config home, so clearing it would silently drop setup.subBlocksstructure and the sub-block value map — the value map is authoritative inmergeSubblockStateWithValues, so clearing only the structure leaves the source's path in effect.duplicateBlock(action-bar duplicate), which bypassesregenerateBlockIdsentirely.findConflictingWebhookPathOwnerignores same-workflow owners andclaimWebhookPath's CAS accepts a same-workflow re-claim.2. Workflow-level copy paths over-cleared (found while auditing #1)
webhookIdfield. Duplicating, forking, or importing a workflow blanked it while leaving thewebhookTokenit pairs with — a silently half-configured copy that fails at runtime with an error pointing nowhere near the copy that caused it.isBlockActingAsTriggerinsanitizeSubBlocksForDuplicate(server duplicate + workspace forking) andupdateBlockReferences(export/import).sanitizeSubBlocksForDuplicate's flag is a required parameter so every call site must decide rather than inheriting a silent default.Why
isBlockActingAsTriggerand nothasTriggerCapabilityVercel and Attio are
category: 'tools'withtriggers.enabled, so capability returnstrueand would defeat the guard. The new predicate istriggerMode === true || category === 'triggers'— exactly deploy's ownresolveTriggerIdgate, so client and server agree on which blocks own a webhook (including for unregistered block types, where both decline and no webhook is created either way).Audited all 10 stripped ids for collisions with action-block subblock ids:
webhookIdis the only real one.scheduleInfoalso collides but belongs to the schedule block, which iscategory: 'triggers'. Display-only system subblocks stay stripped unconditionally — those ids exist only on trigger definitions.Not changed
Trigger blocks behave identically everywhere — they still lose their webhook identity on every copy path. The copilot path is untouched:
edit_workflowalready drops runtime subblock ids in bothvalidation.tsandoperations.tsand merges rather than replaces, so it cannot change a webhook URL.triggerCredentialsremains deliberately preserved so a copy keeps its OAuth connection.Note: existing already-pasted workflows don't self-heal — they now fail with an actionable 400 instead of a 500, and the duplicate trigger blocks need to be re-added once.
Related pre-existing issue left for a separate PR: the generic webhook's auth
token(triggers/generic/webhook.ts:33) is a create-time-only generator, so clones share the source's shared secret.Type of Change
Testing
stores+lib+ee+triggers(633 files);tsc --noEmitclean.check:api-validation,check:boundaries,check:realtime-prune,check:client-boundary,check:utils. The boundary + prune checks specifically confirm importing@/blocksinto the persistence layer doesn't leak intoapps/realtime.webhookId, paste preservestriggerConfig+triggerId+token, 4 deploy-guard cases, 4sanitizeSubBlocksForDuplicatecases (non-trigger preserved, prefixed keys preserved, trigger still stripped, display-only always stripped), 3regenerateWorkflowIdscases.Checklist