{"record":{"id":"4987d2445eb9d934","repo":"windmill-labs/windmill","slug":"flow-json-must-be-an-object","errorCode":null,"errorMessage":"Flow JSON must be an object","messagePattern":"Flow JSON must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts","lineNumber":340,"sourceCode":"\t'modules',\n\t'schema',\n\t'preprocessor_module',\n\t'failure_module',\n\t'groups',\n\t'notes'\n] as const\n\n/**\n * Parse and validate a raw object as an `EditableFlowJson`. Validates module\n * shape, schema shape, optional special modules (with their reserved ids),\n * groups, top-level flow settings, and that no module ids collide.\n */\nexport function validateEditableFlowJson(\n\trawFlow: unknown,\n\tctx: FlowValidationContext = {}\n): EditableFlowJson {\n\tif (!rawFlow || typeof rawFlow !== 'object' || Array.isArray(rawFlow)) {\n\t\tthrow new Error('Flow JSON must be an object')\n\t}\n\n\tconst flow = rawFlow as Record<string, unknown>\n\n\t// Reject unknown top-level keys: silently dropping them would make patch\n\t// tools report success for edits that never land on the flow.\n\tconst allowedKeys = new Set<string>([\n\t\t...EDITABLE_FLOW_STRUCTURAL_KEYS,\n\t\t...FLOW_VALUE_SETTINGS_KEYS\n\t])\n\tconst unknownKeys = Object.keys(flow).filter((key) => !allowedKeys.has(key))\n\tif (unknownKeys.length > 0) {\n\t\tthrow new Error(\n\t\t\t`Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Allowed keys: ${[...allowedKeys].join(', ')}`\n\t\t)\n\t}\n\n\tconst settingsResult = flowValueSettingsSchema.safeParse(flow)","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts#L322-L358","documentation":"validateEditableFlowJson is the entry point for the copilot's flow JSON representation; it first requires the input to be a non-null, non-array object. Anything else — null, undefined, a bare array, a string, a number — cannot represent a flow and is rejected immediately before any field-level checks.","triggerScenarios":"Calling validateEditableFlowJson (via flowTools parsedFlow/patch tools) with null, undefined, an array of modules, or a JSON string instead of an object with `modules` etc.","commonSituations":"An LLM emits a bare array of modules instead of an object; JSON.parse produced a string; an upstream function returned null for a missing flow.","solutions":["Wrap the payload in an object: pass {\"modules\": [...], ...} rather than the modules array itself.","JSON.parse the input first if it is a string.","Handle null/undefined upstream and skip validation for a genuinely absent flow."],"exampleFix":"// before\nvalidateEditableFlowJson([{id:'m1',value:{...}}])\n// after\nvalidateEditableFlowJson({modules:[{id:'m1',value:{...}}]})","handlingStrategy":"type-guard","validationCode":"function assertFlowObject(raw) {\n  if (!raw || typeof raw !== 'object' || Array.isArray(raw))\n    throw new TypeError('Flow JSON must be a non-array object')\n  return raw\n}","typeGuard":"function isEditableFlowJson(v) {\n  return typeof v === 'object' && v !== null && !Array.isArray(v) &&\n    Array.isArray(v.modules)\n}","tryCatchPattern":"try {\n  const flow = validateEditableFlowJson(raw)\n} catch (e) {\n  if (String(e.message) === 'Flow JSON must be an object') {\n    // wrap arrays in {modules: ...} or JSON.parse strings before retrying\n  } else throw e\n}","preventionTips":["Ensure tool payloads are objects with a modules array, never bare arrays","JSON.parse string inputs before validation","Check for null upstream results before calling validation"],"tags":["validation","types","flow-json"],"backgroundTag":"schema-validation-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}