windmill-labs/windmill · error · Error

Draft variable "${draftValue.path}" is secret but stages no

Error message

Draft variable "${draftValue.path}" is secret but stages no value, so it cannot be created. Run write_variable with its value first.

What it means

When deploying a new variable that does not exist yet, a secret variable must carry a staged value. If the draft's value is '' (the 'nothing staged' sentinel), the backend would create an empty secret, so this error tells the model to stage the value first.

Source

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

		path: args.path,
		variable: {
			value,
			is_secret,
			description
		},
		labels: args.labels ?? base?.labels,
		wsSpecific: args.ws_specific ?? base?.wsSpecific ?? false,
		account: args.account ?? base?.account,
		is_oauth: args.is_oauth ?? base?.is_oauth,
		expires_at: args.expires_at ?? base?.expires_at
	}
}

// The deploy body for a variable that does not exist yet.
function buildVariableCreateRequestBody(draftValue: CreateVariable): CreateVariable {
	const requestBody = structuredClone(draftValue)
	if (requestBody.is_secret && requestBody.value === '') {
		throw new Error(
			`Draft variable "${draftValue.path}" is secret but stages no value, so it cannot be created. Run write_variable with its value first.`
		)
	}
	return requestBody
}

// The deploy body for an existing variable. Every field is optional on the update
// endpoint, and a draft stores '' when it stages no new value — so omitting `value` in
// that case is what leaves the stored one untouched. A staged value is sent as-is: the
// endpoint decrypts an `$encrypted:` marker and encrypts plaintext.
function buildVariableUpdateRequestBody(
	draftValue: CreateVariable
): Omit<CreateVariable, 'value'> & { value?: string } {
	const { value, ...rest } = structuredClone(draftValue)
	// A value the draft is never allowed to carry — a secret's, and an OAuth-managed one
	// dropped by `variableToDraftState` — reaches here as '' whenever this edit staged
	// none, so sending it would blank the stored value or wipe a rotating token.
	if (rest.is_secret || rest.is_oauth === true || rest.account != undefined) {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Run write_variable with the actual value for the draft path first, then redeploy
  2. If the variable should not be secret, set is_secret: false so an empty value is allowed

Example fix

// before
deployVariables([{ path: 'u/admin/API_KEY', is_secret: true, value: '' }])
// after
writeVariable({ path: 'u/admin/API_KEY', is_secret: true, value: 'abc123' })
deployVariables([{ path: 'u/admin/API_KEY', is_secret: true, value: 'abc123' }])
Defensive patterns

Strategy: validation

Validate before calling

if (draft.is_secret && draft.value === '') throw new Error('stage the secret value before deploying');

Type guard

function isDeployableSecretDraft(d) { return !d.is_secret || (typeof d.value === 'string' && d.value !== ''); }

Try / catch

try { deployVariables(drafts) } catch (e) { if (e.message.includes('stages no value')) await writeVariable({ path, value }) and redeploy; }

Prevention

When it happens

Trigger: Deploying a draft variable whose requestBody.is_secret is true and value is '' — i.e. buildVariableCreateRequestBody on a create-body with no staged value.

Common situations: The model created a variable draft and forgot to stage a value (or used '' as placeholder) before triggering the deploy.

Related errors


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