windmill-labs/windmill · error

Could not synthesize ${type} draft "${path}".

Error message

Could not synthesize ${type} draft "${path}".

What it means

After storing the draft value, persistGlobalDraft converts the stored UserDraftEntry back into a chat WorkspaceItem via userDraftEntryToWorkspaceItem(). That converter dispatches on itemKind and delegates to per-kind converters (scriptDraftToWorkspaceItem, flowDraftToWorkspaceItem, ...) which return undefined when the value does not match the expected shape for that kind. If no WorkspaceItem can be synthesized, this error is thrown — the in-memory seed is then already dirty, so callers should treat it as an internal consistency failure.

Source

Thrown at frontend/src/lib/components/copilot/chat/global/userDraftAdapter.ts:439

	const itemKind = itemKindFor(type, opts.triggerKind)
	if (!itemKind) throw new Error(`Unsupported draft type "${type}".`)
	const storagePath = resolveDraftStoragePath(workspace, itemKind, path)
	UserDraft.seed(itemKind, storagePath, value, { workspace })
	await UserDraftDbSyncer.save({
		workspace,
		itemKind,
		path: storagePath,
		value,
		immediate: true,
		force: opts.force
	})
	const { displayPath, isLiveDraft } = liveDisplayPath(workspace, itemKind, storagePath)
	const item = userDraftEntryToWorkspaceItem(
		{ workspace, itemKind, path: storagePath, value },
		displayPath,
		isLiveDraft
	)
	if (!item) throw new Error(`Could not synthesize ${type} draft "${path}".`)
	// A failed save (network/5xx) is recorded in the syncer's failure map, not
	// thrown — so check it before reporting success, else a write tool would tell
	// the chat "saved" while the DB-backed source of truth was never updated.
	const saveState = UserDraftDbSyncer.getState({ workspace, itemKind, path: storagePath })
	if (saveState.state === 'failed') {
		return {
			status: 'error',
			item,
			itemKind,
			storagePath,
			message: saveState.failureMessage ?? 'Draft save failed'
		}
	}
	const conflict = opts.force
		? undefined
		: UserDraftDbSyncer.getConflict({ workspace, itemKind, path: storagePath }).conflict
	if (conflict) {
		return {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Validate the draft value against the kind's expected shape (NewScript, Flow, AppDraftValue, ResourceDraftState, etc.) before calling persistGlobalDraft.
  2. Log entry.itemKind and the value when this fires to see which converter returned undefined.
  3. If type is a trigger, ensure its draft kind is chat-addressable (has an entry in TRIGGER_KIND_BY_DRAFT_KIND) and the value matches TriggerRequestBody.
  4. Extend the corresponding xxxDraftToWorkspaceItem converter if a legitimate new shape was rejected.

Example fix

// before
await persistGlobalDraft(ws, 'script', path, value)
// after
if (!value || typeof value !== 'object' || !('content' in value)) throw new Error('Invalid script draft value')
await persistGlobalDraft(ws, 'script', path, value)
Defensive patterns

Strategy: validation

Validate before calling

function isPlausibleDraftValue(type: WorkspaceItemType, value: unknown): boolean {
  return typeof value === 'object' && value !== null && Object.keys(value).length > 0
}
if (!isPlausibleDraftValue(type, value)) throw new Error(`Malformed ${type} draft value`)

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v)
}

Try / catch

try {
  await persistGlobalDraft(workspace, type, path, value, opts)
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Could not synthesize')) {
    console.error('Draft value rejected by converter', { type, path, value })
    notify(`The ${type} draft value is not in the expected format`)
  } else throw e
}

Prevention

When it happens

Trigger: persistGlobalDraft called with a value whose shape fails the per-kind converter (e.g. a script draft value missing the fields scriptDraftToWorkspaceItem requires, a malformed app/flow value); or an itemKind that has no converter (chat-unaddressable kinds like webhook/poll/cli trigger drafts reaching the write path).

Common situations: An AI chat write tool receives a loosely-typed JSON 'value' argument from the model and forwards it unvalidated; version drift where a converter was tightened but callers still send the old shape.

Related errors


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