diff --git a/apps/sim/lib/workflows/credentials/credential-extractor.ts b/apps/sim/lib/workflows/credentials/credential-extractor.ts index df1812872..014febabc 100644 --- a/apps/sim/lib/workflows/credentials/credential-extractor.ts +++ b/apps/sim/lib/workflows/credentials/credential-extractor.ts @@ -161,6 +161,49 @@ function formatFieldName(fieldName: string): string { .join(' ') } +/** + * Remove malformed subBlocks from a block that may have been created by bugs. + * This includes subBlocks with: + * - Key "undefined" (caused by assigning to undefined key) + * - Missing required `id` field + * - Type "unknown" (indicates malformed data) + */ +function removeMalformedSubBlocks(block: any): void { + if (!block.subBlocks) return + + const keysToRemove: string[] = [] + + Object.entries(block.subBlocks).forEach(([key, subBlock]: [string, any]) => { + // Flag subBlocks with invalid keys (literal "undefined" string) + if (key === 'undefined') { + keysToRemove.push(key) + return + } + + // Flag subBlocks that are null or not objects + if (!subBlock || typeof subBlock !== 'object') { + keysToRemove.push(key) + return + } + + // Flag subBlocks with type "unknown" (malformed data) + if (subBlock.type === 'unknown') { + keysToRemove.push(key) + return + } + + // Flag subBlocks missing required id field + if (!subBlock.id) { + keysToRemove.push(key) + } + }) + + // Remove the flagged keys + keysToRemove.forEach((key) => { + delete block.subBlocks[key] + }) +} + /** * Sanitize workflow state by removing all credentials and workspace-specific data * This is used for both template creation and workflow export to ensure consistency @@ -183,6 +226,9 @@ export function sanitizeWorkflowForSharing( Object.values(sanitized.blocks).forEach((block: any) => { if (!block?.type) return + // First, remove any malformed subBlocks that may have been created by bugs + removeMalformedSubBlocks(block) + const blockConfig = getBlock(block.type) // Process subBlocks with config diff --git a/apps/sim/stores/workflows/json/importer.ts b/apps/sim/stores/workflows/json/importer.ts index bb119e989..f2243c76c 100644 --- a/apps/sim/stores/workflows/json/importer.ts +++ b/apps/sim/stores/workflows/json/importer.ts @@ -5,9 +5,14 @@ import type { WorkflowState } from '../workflow/types' const logger = createLogger('WorkflowJsonImporter') /** - * Normalize subblock values by converting empty strings to null. + * Normalize subblock values by converting empty strings to null and filtering out invalid subblocks. * This provides backwards compatibility for workflows exported before the null sanitization fix, * preventing Zod validation errors like "Expected array, received string". + * + * Also filters out malformed subBlocks that may have been created by bugs in previous exports: + * - SubBlocks with key "undefined" (caused by assigning to undefined key) + * - SubBlocks missing required fields like `id` + * - SubBlocks with `type: "unknown"` (indicates malformed data) */ function normalizeSubblockValues(blocks: Record): Record { const normalizedBlocks: Record = {} @@ -19,6 +24,34 @@ function normalizeSubblockValues(blocks: Record): Record = {} Object.entries(block.subBlocks).forEach(([subBlockId, subBlock]: [string, any]) => { + // Skip subBlocks with invalid keys (literal "undefined" string) + if (subBlockId === 'undefined') { + logger.warn(`Skipping malformed subBlock with key "undefined" in block ${blockId}`) + return + } + + // Skip subBlocks that are null or not objects + if (!subBlock || typeof subBlock !== 'object') { + logger.warn(`Skipping invalid subBlock ${subBlockId} in block ${blockId}: not an object`) + return + } + + // Skip subBlocks with type "unknown" (malformed data) + if (subBlock.type === 'unknown') { + logger.warn( + `Skipping malformed subBlock ${subBlockId} in block ${blockId}: type is "unknown"` + ) + return + } + + // Skip subBlocks missing required id field + if (!subBlock.id) { + logger.warn( + `Skipping malformed subBlock ${subBlockId} in block ${blockId}: missing id field` + ) + return + } + const normalizedSubBlock = { ...subBlock } // Convert empty strings to null for consistency