windmill-labs/windmill · error · Error

An empty string is not a valid value for secret variable "${

Error message

An empty string is not a valid value for secret variable "${args.path}". Omit value to keep the stored secret, or pass the real new one.

What it means

An empty string in the secret draft means 'nothing staged', so it cannot also mean 'set the secret to empty'. Passing value='' for a secret variable is rejected to prevent a placeholder habit from wiping the stored secret.

Source

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

function resolveVariableWrite(
	args: WriteVariableArgs,
	base?: VariableDraftState
): {
	is_secret: boolean
	value: string
	description: string
} {
	if (base === undefined && (args.value === undefined || args.is_secret === undefined)) {
		throw new Error(
			`Variable "${args.path}" does not exist yet, so creating it requires both value and is_secret.`
		)
	}
	const is_secret = args.is_secret ?? base?.variable.is_secret ?? false
	// '' is the sentinel for "nothing staged" in a secret draft, so it cannot also mean
	// "set the secret to empty". Refusing it matters because a model reaching for a
	// placeholder — the habit this schema change removes — would otherwise wipe the secret.
	if (is_secret && args.value === '') {
		throw new Error(
			`An empty string is not a valid value for secret variable "${args.path}". Omit value to keep the stored secret, or pass the real new one.`
		)
	}
	// Securing one needs a value too when it holds none: the deploy would send no `value`
	// (nothing is staged) and the backend refuses an is_secret change without one. Saying
	// so here keeps the model from having to interpret that error.
	if (
		is_secret &&
		base?.variable.is_secret === false &&
		(args.value ?? base.variable.value) === ''
	) {
		throw new Error(
			`Cannot make variable "${args.path}" secret without a value: it currently holds an empty one, so there would be nothing to encrypt. Pass the value it should hold.`
		)
	}
	// Un-securing always needs a new plaintext value. An `$encrypted:` marker is no
	// help: the deploy endpoints only decrypt it while the target stays secret, so
	// carrying it into a non-secret variable would store the marker as the value.

View on GitHub (pinned to e474e8803c)

Solutions

  1. Omit the value field entirely to keep the currently stored secret
  2. Pass the actual new secret value if the secret should change

Example fix

// before
writeVariable({ path: 'u/admin/API_KEY', is_secret: true, value: '' })
// after
writeVariable({ path: 'u/admin/API_KEY', is_secret: true }) // keeps stored secret
Defensive patterns

Strategy: validation

Validate before calling

if (isSecret && args.value === '') throw new Error('omit value to keep stored secret');

Type guard

function isValidSecretValue(v) { return v === undefined || (typeof v === 'string' && v.length > 0); }

Try / catch

try { writeVariable(args) } catch (e) { if (e.message.includes('empty string is not a valid value')) delete args.value and retry; }

Prevention

When it happens

Trigger: A write_variable call where the resolved is_secret is true and args.value is exactly '' (empty string).

Common situations: The model uses '' as a placeholder value for an unknown secret instead of omitting value to keep the stored one.

Related errors


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