{"record":{"id":"281a1fec93cd5f0c","repo":"pulumi/pulumi","slug":"hook-s-ondryrun-must-be-a-boolean","errorCode":null,"errorMessage":"hook %s: onDryRun must be a boolean","messagePattern":"hook (.+?): onDryRun must be a boolean","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/pcl/runtime/interpreter.go","lineNumber":202,"sourceCode":"// registerHookNode registers a named hook block with the engine, then stores the hook's\n// registered name as a string variable so it can be referenced in resource hooks options.\n// The command expression is evaluated lazily at invocation time so that `args.X` references\n// can be resolved against the resource's actual args.\nfunc (i *Interpreter) registerHookNode(ctx context.Context, h *pcl.Hook) error {\n\tif h.Command == nil {\n\t\treturn fmt.Errorf(\"hook %s: missing command\", h.Name())\n\t}\n\n\tonDryRun := false\n\tif h.OnDryRun != nil {\n\t\todrVal, _, diags := i.evalContext.Evaluate(h.OnDryRun)\n\t\tif diags.HasErrors() {\n\t\t\treturn diags\n\t\t}\n\t\tif odrVal.IsBool() {\n\t\t\tonDryRun = odrVal.BoolValue()\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"hook %s: onDryRun must be a boolean\", h.Name())\n\t\t}\n\t}\n\n\tignoreErrors := false\n\tif h.IgnoreErrors != nil {\n\t\tieVal, _, diags := i.evalContext.Evaluate(h.IgnoreErrors)\n\t\tif diags.HasErrors() {\n\t\t\treturn diags\n\t\t}\n\t\tif ieVal.IsBool() {\n\t\t\tignoreErrors = ieVal.BoolValue()\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"hook %s: ignoreErrors must be a boolean\", h.Name())\n\t\t}\n\t}\n\n\thookName := i.effectiveName(h.LogicalName())\n\tcmdExpr := h.Command","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/pulumi/pulumi/blob/793f7b2e160db4321fb7fb6b0607461e01cb251e/pkg/pcl/runtime/interpreter.go#L184-L220","documentation":"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\".","triggerScenarios":"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.","commonSituations":"Quoted booleans in config (\"true\" strings from environment variables or config.get), interpolation producing strings, variables typed incorrectly in the PCL program.","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"],"exampleFix":"// before\nhook \"h\" { command = [...], onDryRun = \"true\" }\n// after\nhook \"h\" { command = [...], onDryRun = true }","handlingStrategy":"validation","validationCode":"// PCL: ensure boolean literal\nonDryRun = true  // not \"true\"\n// Go: pre-check evaluated type\nif v := eval(expr); !v.IsBool() { return errors.New(\"onDryRun must be bool\") }","typeGuard":null,"tryCatchPattern":"if err := i.registerHookNode(ctx, h); err != nil {\n    if strings.Contains(err.Error(), \"onDryRun must be a boolean\") { /* fix program type */ }\n}","preventionTips":["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"],"tags":["pulumi","pcl","hooks","type-mismatch"],"backgroundTag":"type-mismatch-expected-bool","analyzedSha":"793f7b2e160db4321fb7fb6b0607461e01cb251e","analyzedAt":"2026-08-31T09:36:43.099Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}