windmill-labs/windmill · error

Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Al

Error message

Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Allowed keys: ${[...allowedKeys].join(', ')}

What it means

After confirming the flow is an object, validateEditableFlowJson rejects any top-level key outside the allowed set (EDITABLE_FLOW_STRUCTURAL_KEYS plus FLOW_VALUE_SETTINGS_KEYS). Unknown keys are not silently dropped, because a patch tool would then report success for an edit that never landed on the flow.

Source

Thrown at frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts:353

export function validateEditableFlowJson(
	rawFlow: unknown,
	ctx: FlowValidationContext = {}
): EditableFlowJson {
	if (!rawFlow || typeof rawFlow !== 'object' || Array.isArray(rawFlow)) {
		throw new Error('Flow JSON must be an object')
	}

	const flow = rawFlow as Record<string, unknown>

	// Reject unknown top-level keys: silently dropping them would make patch
	// tools report success for edits that never land on the flow.
	const allowedKeys = new Set<string>([
		...EDITABLE_FLOW_STRUCTURAL_KEYS,
		...FLOW_VALUE_SETTINGS_KEYS
	])
	const unknownKeys = Object.keys(flow).filter((key) => !allowedKeys.has(key))
	if (unknownKeys.length > 0) {
		throw new Error(
			`Unknown top-level flow key(s): ${unknownKeys.join(', ')}. Allowed keys: ${[...allowedKeys].join(', ')}`
		)
	}

	const settingsResult = flowValueSettingsSchema.safeParse(flow)
	if (!settingsResult.success) {
		const issue = settingsResult.error.issues[0]
		const path = issue?.path?.join('.') ?? 'settings'
		throw new Error(`Invalid flow setting ${path}: ${issue?.message ?? 'unknown error'}`)
	}
	const settings = pickFlowValueSettings(settingsResult.data)

	const modules = validateFlowModules(flow.modules, ctx)
	const schema = validateFlowSchema(flow.schema)
	const preprocessorModule = validateOptionalFlowModule(
		flow.preprocessor_module,
		'preprocessor_module'
	)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Remove all listed unknown keys and keep only allowed ones (modules, schema, preprocessor_module, failure_module, groups, notes, and the flow-value settings keys).
  2. Move flow metadata (name, path, description) to the appropriate deploy/API call, not the editable flow JSON.
  3. Unwrap any wrapper object so the flow object itself is passed, not {flow: {...}}.

Example fix

// before
{"name":"myflow","modules":[...]}
// after
{"modules":[...]}
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = new Set(['modules','schema','preprocessor_module','failure_module','groups','notes' /* + FLOW_VALUE_SETTINGS_KEYS */])
const unknown = Object.keys(flow).filter(k => !ALLOWED.has(k))
if (unknown.length) console.warn('Strip unknown keys:', unknown)

Try / catch

try {
  const editable = validateEditableFlowJson(raw)
} catch (e) {
  if (String(e.message).startsWith('Unknown top-level flow key')) {
    const keys = /key\(s\): ([^.]+)\./.exec(String(e.message))?.[1]?.split(', ') ?? []
    keys.forEach(k => delete raw[k])
    return validateEditableFlowJson(raw)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling flowTools parsedFlow/patch tools with top-level keys like "name", "description", "path", "tags", or nested wrapper objects (e.g. {"flow":{...}}) that are not part of the editable representation.

Common situations: An LLM echoes back extra fields from a GET response (summary, path, version); a user pastes a full flow export including metadata; the payload is wrapped in an extra object level.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/76947a99a8a9fe5d. Report an issue: GitHub.