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.CommandView on GitHub (pinned to 793f7b2e16)
Solutions
- Change the onDryRun value to a real boolean: onDryRun = true instead of onDryRun = "true"
- If sourcing from config, parse the string to bool before assigning (e.g. compare == "true")
- Check the type of the variable/expression referenced by onDryRun
- 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
- Use boolean literals or bool-typed variables for onDryRun
- Convert string config values (== "true") instead of assigning them directly
- Avoid interpolations that yield strings where bools are expected
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
- hook %s: ignoreErrors must be a boolean
- hook %s: missing command
- hook %s: failed to unmarshal %s: %w
- hook %s: evaluating command: %v
- convert invoke arguments: %w
AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31).
Data as JSON: /api/errors/281a1fec93cd5f0c.
Report an issue: GitHub.