windmill-labs/windmill · error · Error

Cannot turn secret variable "${args.path}" into a non-secret

Error message

Cannot turn secret variable "${args.path}" into a non-secret one without a value: its stored value cannot be read, so it would be replaced by an empty one. Pass the new plaintext value, or leave is_secret unset to keep it secret.

What it means

Un-securing a secret variable requires a new plaintext value because the stored value is unreadable ($encrypted marker) outside a secret target. Without a value the variable would be replaced by an empty one, so the tool refuses and asks for plaintext or to leave is_secret unset.

Source

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

		)
	}
	// 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.
	if (is_secret === false && base?.variable.is_secret === true && args.value === undefined) {
		throw new Error(
			`Cannot turn secret variable "${args.path}" into a non-secret one without a value: its stored value cannot be read, so it would be replaced by an empty one. Pass the new plaintext value, or leave is_secret unset to keep it secret.`
		)
	}
	return {
		is_secret,
		value: args.value ?? base?.variable.value ?? '',
		description: args.description ?? base?.variable.description ?? ''
	}
}

function createVariableToDraftState(
	args: WriteVariableArgs,
	base?: VariableDraftState
): VariableDraftState {
	const { is_secret, value, description } = resolveVariableWrite(args, base)
	return {
		...base,
		path: args.path,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Pass the new plaintext value together with is_secret: false
  2. Leave is_secret unset (keep the variable secret) if you don't have the plaintext
  3. Store the secret's value elsewhere first if you need it in non-secret form

Example fix

// before
writeVariable({ path: 'u/admin/API_KEY', is_secret: false })
// after
writeVariable({ path: 'u/admin/API_KEY', is_secret: false, value: 'plain-value' })
Defensive patterns

Strategy: validation

Validate before calling

if (args.is_secret === false && baseIsSecret && args.value === undefined) {
  throw new Error('unsecuring needs a plaintext value');
}

Type guard

function canUnsecret(args) { return args.is_secret !== false || typeof args.value === 'string'; }

Try / catch

try { writeVariable(args) } catch (e) { if (e.message.includes('into a non-secret')) supplyPlaintextOrAbort(); }

Prevention

When it happens

Trigger: A write_variable call where args.is_secret === false and the base variable is_secret is true and args.value is undefined.

Common situations: The model tries to make a secret readable for debugging without realizing the plaintext is unrecoverable from the draft.

Related errors


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