pulumi/pulumi · error

hook %s: onDryRun must be a boolean

Error message

hook %s: onDryRun must be a boolean

What it means

Hook blocks accept an optional onDryRun option controlling whether the hook runs during previews. The interpreter evaluates this expression eagerly at registration time and requires the result to be a boolean. If it evaluates to any other cty type (string, number, null...), registration fails with "hook <name>: onDryRun must be a boolean".

Source

Thrown at pkg/pcl/runtime/interpreter.go:202

// registerHookNode registers a named hook block with the engine, then stores the hook's
// registered name as a string variable so it can be referenced in resource hooks options.
// The command expression is evaluated lazily at invocation time so that `args.X` references
// can be resolved against the resource's actual args.
func (i *Interpreter) registerHookNode(ctx context.Context, h *pcl.Hook) error {
	if h.Command == nil {
		return fmt.Errorf("hook %s: missing command", h.Name())
	}

	onDryRun := false
	if h.OnDryRun != nil {
		odrVal, _, diags := i.evalContext.Evaluate(h.OnDryRun)
		if diags.HasErrors() {
			return diags
		}
		if odrVal.IsBool() {
			onDryRun = odrVal.BoolValue()
		} else {
			return fmt.Errorf("hook %s: onDryRun must be a boolean", h.Name())
		}
	}

	ignoreErrors := false
	if h.IgnoreErrors != nil {
		ieVal, _, diags := i.evalContext.Evaluate(h.IgnoreErrors)
		if diags.HasErrors() {
			return diags
		}
		if ieVal.IsBool() {
			ignoreErrors = ieVal.BoolValue()
		} else {
			return fmt.Errorf("hook %s: ignoreErrors must be a boolean", h.Name())
		}
	}

	hookName := i.effectiveName(h.LogicalName())
	cmdExpr := h.Command

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Change the onDryRun value to a real boolean: onDryRun = true instead of onDryRun = "true"
  2. If sourcing from config, parse the string to bool before assigning (e.g. compare == "true")
  3. Check the type of the variable/expression referenced by onDryRun
  4. Add type annotations/validation in the program so the value is statically a bool

Example fix

// before
hook "h" { command = [...], onDryRun = "true" }
// after
hook "h" { command = [...], onDryRun = true }
Defensive patterns

Strategy: validation

Validate before calling

// PCL: ensure boolean literal
onDryRun = true  // not "true"
// Go: pre-check evaluated type
if v := eval(expr); !v.IsBool() { return errors.New("onDryRun must be bool") }

Try / catch

if err := i.registerHookNode(ctx, h); err != nil {
    if strings.Contains(err.Error(), "onDryRun must be a boolean") { /* fix program type */ }
}

Prevention

When it happens

Trigger: registerHookNode (pkg/pcl/runtime/interpreter.go:202): i.evalContext.Evaluate(h.OnDryRun) succeeds but odrVal.IsBool() is false — e.g. onDryRun = "true" (a string) or a number instead of a bool literal/bool-typed variable.

Common situations: Quoted booleans in config ("true" strings from environment variables or config.get), interpolation producing strings, variables typed incorrectly in the PCL program.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/281a1fec93cd5f0c. Report an issue: GitHub.