windmill-labs/windmill · error · Error

Draft flow "${path}" has no value.

Error message

Draft flow "${path}" has no value.

What it means

When reading a flow draft, the code expects the draft's value to be a structured FlowDraftValue object. If the draft exists but its value is undefined or only a string, it cannot produce a usable flow representation, so it throws. This keeps the model from treating a placeholder or serialized draft as a real flow value.

Source

Thrown at frontend/src/lib/components/copilot/chat/global/core.ts:5158

	return writeScriptDraft(
		{
			path,
			summary: base.summary,
			language: base.language,
			content: updated
		},
		ctx
	)
}

async function loadFlowDraftValue(
	path: string,
	workspace: string
): Promise<{ flow: FlowDraftValue; summary?: string }> {
	const draft = await getGlobalDraft(workspace, 'flow', path)
	if (draft) {
		if (draft.value === undefined || typeof draft.value === 'string') {
			throw new Error(`Draft flow "${path}" has no value.`)
		}
		return { flow: draft.value as FlowDraftValue, summary: draft.summary }
	}
	const flow = await FlowService.getFlowByPath({ workspace, path })
	return {
		flow: { value: flow.value, schema: flow.schema, groups: flow.value.groups ?? null },
		summary: flow.summary
	}
}

/**
 * `write_flow` accepts `inline_script.<id>` placeholders so the model can
 * overwrite a flow without re-sending (or even reading) unchanged rawscript
 * bodies. Resolve them against the current draft/deployed flow: an id with a
 * stored body keeps it, a new module's own-id placeholder becomes an empty body
 * (to fill via set_flow_module_code), and anything else rejects the write.
 */
async function resolveWriteFlowInlineScripts(

View on GitHub (pinned to e474e8803c)

Solutions

  1. Delete the corrupted/empty flow draft and read again so it falls back to the deployed flow.
  2. Re-run write_flow with a complete flow value to overwrite the draft.
  3. Use patch_flow_json only after a valid draft or deployed flow exists.

Example fix

// before
draftStore.set(workspace, 'flow', path, { value: JSON.stringify(flow) }) // string value rejected
// after
draftStore.set(workspace, 'flow', path, { value: flowValue }) // structured FlowDraftValue
Defensive patterns

Strategy: type-guard

Validate before calling

const draft = await getGlobalDraft(workspace, 'flow', path)
if (draft && (draft.value === undefined || typeof draft.value === 'string')) {
  await deleteGlobalDraft(workspace, 'flow', path) // fall back to deployed flow
}

Type guard

function isFlowDraftValue(v: unknown): v is FlowDraftValue {
  return !!v && typeof v === 'object' && !Array.isArray(v) && 'value' in (v as object)
}

Try / catch

try {
  const { flow } = await readFlow(path)
} catch (e) {
  if (String(e.message).includes('has no value')) {
    await deleteDraft('flow', path)
    const flow = await FlowService.getFlowByPath({ workspace, path })
  } else throw e
}

Prevention

When it happens

Trigger: getGlobalDraft(workspace, 'flow', path) returns a draft with `value === undefined` or a string value — e.g. a flow draft created without a value payload or with a stale string body.

Common situations: A write_flow call was interrupted after registering the draft but before writing the value; an older draft format stored the flow as a JSON string that the new reader no longer accepts; a flow draft was created for a path but value construction failed upstream.

Related errors


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