Repository: gohugoio/hugo (language: go)

Issue:
npx cannot be disabled from exec
### What version of Hugo are you using (`hugo version`)?

`hugo v0.140.2-aae02ca612a02e085c08366a9c9279f4abb39d94+extended linux/amd64 BuildDate=2024-12-30T15:01:53Z VendorInfo=snap:0.140.2`

### Does this issue reproduce with the latest release?

Yes.

### Problem: 

I'm integrating my newly created project with TailwindCSS v4. The Hugo site documentation for TailwindCSS [Found here](https://gohugo.io/functions/css/tailwindcss/), says that I could use the npm OR the as a standalone executable of the CLI. It is currently not working as I imagined. Looking at the Hugo source code and trying to figure it out, understanding the inner workings, I found something interesting:

On the repository file `hugo/resources/resource_transformers/cssjs/tailwindcss.go`, line 109 executes a command with the Npx function. The function can be found here: `hugo/common/hexec/exec.go`. In the header of the function we have:

```go
// Npx will in order:
// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
// 2. If not found, and npx is available, run npx --no-install <name> <args>.
// 3. Fall back to the PATH.
```

This is a good sequence, but has a problem. I'm using the standalone CLI (or at least I want that), `npx` is installed in my system, because of other projects, but following this approach, it seems that the PATH fallback is never called. When I try to run `hugo server --logLevel debug` I get this snippet of error:

```sh
INFO  tailwindcss: npm ERR! canceled
INFO  tailwindcss: 
INFO  tailwindcss: npm
INFO  tailwindcss: 
INFO  tailwindcss: ERR!
INFO  tailwindcss: A complete log of this run can be found in:
INFO  tailwindcss: npm ERR!     /home/diego/snap/hugo/22151/.npm/_logs/2025-01-06T18_36_16_231Z-debug-0.log

Error: error building site: TAILWINDCSS: failed to transform "/css/main.css" (text/css): npm ERR! canceled

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/diego/snap/hugo/22151/.npm/_logs/2025-01-06T18_36_16_231Z-debug-0.log
```

Looking further on the logs on `/home/diego/snap/hugo/22151/.npm/_logs/2025-01-06T18_36_16_231Z-debug-0.log` I have this:

```log
0 verbose cli /snap/hugo/22151/usr/bin/node /snap/hugo/22151/usr/lib/node_modules/npm/bin/npm-cli.js
1 info using npm@8.19.4
2 info using node@v16.20.2
3 timing npm:load:whichnode Completed in 0ms
4 timing config:load:defaults Completed in 1ms
5 timing config:load:file:/snap/hugo/22151/usr/lib/node_modules/npm/npmrc Completed in 1ms
6 timing config:load:builtin Completed in 1ms
7 timing config:load:cli Completed in 1ms
8 timing config:load:env Completed in 0ms
9 timing config:load:file:/home/diego/Projects/penielveiculos/penielveiculos-website/.npmrc Completed in 1ms
10 timing config:load:project Completed in 5ms
11 timing config:load:file:/home/diego/snap/hugo/22151/.npmrc Completed in 0ms
12 timing config:load:user Completed in 0ms
13 timing config:load:file:/snap/hugo/22151/usr/etc/npmrc Completed in 0ms
14 timing config:load:global Completed in 0ms
15 timing config:load:validate Completed in 0ms
16 timing config:load:credentials Completed in 6ms
17 timing config:load:setEnvs Completed in 1ms
18 timing config:load Completed in 16ms
19 timing npm:load:configload Completed in 16ms
20 timing npm:load:mkdirpcache Completed in 1ms
21 timing npm:load:mkdirplogs Completed in 0ms
22 verbose title npm exec tailwindcss --input=- --cwd /home/diego/Projects/penielveiculos/penielveiculos-website --minify
23 verbose argv "exec" "--yes" "false" "--" "tailwindcss" "--input=-" "--cwd" "/home/diego/Projects/penielveiculos/penielveiculos-website" "--minify"
24 timing npm:load:setTitle Completed in 1ms
25 timing config:load:flatten Completed in 2ms
26 timing npm:load:display Completed in 2ms
27 verbose logfile logs-max:10 dir:/home/diego/snap/hugo/22151/.npm/_logs
28 verbose logfile /home/diego/snap/hugo/22151/.npm/_logs/2025-01-06T18_36_16_231Z-debug-0.log
29 timing npm:load:logFile Completed in 3ms
30 timing npm:load:timers Completed in 0ms
31 timing npm:load:configScope Completed in 0ms
32 timing npm:load Completed in 23ms
33 silly logfile start cleaning logs, removing 1 files
34 silly logfile done cleaning log files
35 timing arborist:ctor Completed in 1ms
36 http fetch GET 200 https://registry.npmjs.org/tailwindcss 476ms (cache updated)
37 timing arborist:ctor Completed in 0ms
38 timing arborist:ctor Completed in 0ms
39 timing command:exec Completed in 713ms
40 verbose stack Error: canceled
40 verbose stack     at exec (/snap/hugo/22151/usr/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:230:17)
40 verbose stack     at async module.exports (/snap/hugo/22151/usr/lib/node_modules/npm/lib/cli.js:78:5)
41 verbose cwd /home/diego/Projects/penielveiculos/penielveiculos-website
42 verbose Linux 5.15.167.4-microsoft-standard-WSL2
43 verbose node v16.20.2
44 verbose npm  v8.19.4
45 error canceled
46 verbose exit 1
47 timing npm Completed in 820ms
48 verbose code 1
49 error A complete log of this run can be found in:
49 error     /home/diego/snap/hugo/22151/.npm/_logs/2025-01-06T18_36_16_231Z-debug-0.log
````

It is clear that it is trying to use npm under the hood. But it is not what it should do, at least for my needs.

I've found in ChatGPT (I don't know where it pick this from, or if it made it up, but it told me to add this to the `partials/head/css.html` of my theme skeleton:

```html
{{- $opts := dict "binary" "tailwindcss" "minify" true }}
```

I will be honest that I didn't find in the source code of Hugo where this could be getting into, but I've tried for peace of mind. Same thing, It didn't work (as I suspected).

With all this information I think that could be a "bug", or at least a lack of configuration, for enabling the use of TailwindCSS without the Npx exec function.

Am I crazy, of this make sense?


Discussion / hints:
Looking at the code we do have a third fallback (PATH) if  NPX fails, which I suspect it doesn't do in your case. 

>It is clear that it is trying to use npm under the hood. But it is not what it should do, at least for my needs.

NPX is, as I understand it, bundled into NPM.

>I've found in ChatGPT 

ChatGPT is doing a lot of guessing.
My first thought is to test this setup with something other than the snap package.

The Hugo snap package is [strictly confined](https://snapcraft.io/docs/snap-confinement). Our snap package includes Node, Dart Sass, and other apps to which we shell out. Our snap package does not include the TailwindCSS  standalone CLI. And I don't see that changing unless they provide executables for all the platforms that our Snap can run on.
> Looking at the code we do have a third fallback (PATH) if NPX fails, which I suspect it doesn't do in your case.
> 
> > It is clear that it is trying to use npm under the hood. But it is not what it should do, at least for my needs.
> 
> NPX is, as I understand it, bundled into NPM.
> 
> > I've found in ChatGPT
> 
> ChatGPT is doing a lot of guessing.

I imagined that about ChatGPT, it looked a lot of a made-up response. But I'm learning Hugo, so could be something that exists, and I didn't know about.  I will try the @jmooring recommendation. I will try Hugo binary directly. But for what it look like, it's like the PATH is only fallback if `npm` is not installed (and yes, `npx` is part of `npm`), witch is not my case, it is, but I don't want to use it.
> My first thought is to test this setup with something other than the snap package.
> 
> The Hugo snap package is [strictly confined](https://snapcraft.io/docs/snap-confinement). Our snap package includes Node, Dart Sass, and other apps to which we shell out. Our snap package does not include the TailwindCSS standalone CLI. And I don't see that changing unless they provide executables for all the platforms that our Snap can run on.

Thanks for your orientation @jmooring, but it didn't work. Here are the logs:

```sh
Watching for changes in /home/diego/Projects/penielveiculos/penielveiculos-website/{archetypes,assets,content,data,i18n,layouts,static,themes}
Watching for config changes in /home/diego/Projects/penielveiculos/penielveiculos-website/hugo.yaml, /home/diego/Projects/penielveiculos/penielveiculos-website/themes/penielveiculos/hugo.yaml
Start building sites … 
hugo v0.140.2-aae02ca612a02e085c08366a9c9279f4abb39d94+extended linux/amd64 BuildDate=2024-12-30T15:01:53Z VendorInfo=gohugoio

Built in 1394 ms
Error: error building site: TAILWINDCSS: failed to transform "/css/main.css" (text/css): npm error npx canceled due to missing packages and no YES option: ["tailwindcss@3.4.17"]
npm notice
npm notice New major version of npm available! 10.9.0 -> 11.0.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.0.0
npm notice To update run: npm install -g npm@11.0.0
npm notice
npm error A complete log of this run can be found in: /home/diego/.npm/_logs/2025-01-06T21_02_40_226Z-debug-0.log
```
> npm error npx canceled due to missing packages and no YES option:

Yea, right ... We check that `npx` exists, but not that it succeeds, which we should probably do.

If you want a global `tailwindcss` install, have you considered:

```
npm install -g tailwindcss@next
```

That should work with `npx`.
> > npm error npx canceled due to missing packages and no YES option:
> 
> Yea, right ... We check that `npx` exists, but not that it succeeds, which we should probably do.
> 
> If you want a global `tailwindcss` install, have you considered:
> 
> ```
> npm install -g tailwindcss@next
> ```
> 
> That should work with `npx`.

Yeah, this is a solution that I thought of since the beginning. But the problem is that, with TailwindCSS v4 (which is a total rewrite using Rust), we now have the option to ditch the node ecosystem completely. And this is my goal. I have the tailwindcss binary globally on my system already, it is in my PATH. The binary replaces all the postcss external need and the other plugins, it is self contained like Hugo, this is a huge benefit.

Do you understand my point?

And I think a lot of folks now and in the future will go in that direction.
On Linux, _not_ using a snap, with the standalone Tailwind CLI installed (named tailwindcss and in my PATH)...

```text
jmooring@ubuntu:~/code/hugo-docker-test (main)$ hugo

Start building sites … 
hugo v0.141.0-DEV-b7b49fb0f8302c22aa983505546c9f14bea6687e+extended linux/amd64 BuildDate=2025-01-06T14:07:05Z

ERROR TAILWINDCSS: failed to transform "/temp/css" (application/octet-stream). You need to install TailwindCSS CLI. See https://gohugo.io/functions/css/tailwindcss/: binary with name "tailwindcss" not found using npx
Total in 508 ms
Error: error building site: render: failed to render pages: render of "/home/jmooring/code/hugo-docker-test/content/other-tests/tailwindcss.md" failed: "/home/jmooring/code/hugo-docker-test/layouts/_default/tailwindcss.html:16:23": execute of template failed: template: _default/tailwindcss.html:16:23: executing "main" at <.Content>: error calling Content: TAILWINDCSS: failed to transform "/temp/css" (application/octet-stream). You need to install TailwindCSS CLI. See https://gohugo.io/functions/css/tailwindcss/: binary with name "tailwindcss" not found using npx

jmooring@ubuntu:~/code/hugo-docker-test (main)$ tailwindcss -v
≈ tailwindcss v4.0.0-beta.8
```
> On Linux, _not_ using a snap, with the standalone Tailwind CLI installed (named tailwindcss and in my PATH)...
> 
> ```
> jmooring@ubuntu:~/code/hugo-docker-test (main)$ hugo
> 
> Start building sites … 
> hugo v0.141.0-DEV-b7b49fb0f8302c22aa983505546c9f14bea6687e+extended linux/amd64 BuildDate=2025-01-06T14:07:05Z
> 
> ERROR TAILWINDCSS: failed to transform "/temp/css" (application/octet-stream). You need to install TailwindCSS CLI. See https://gohugo.io/functions/css/tailwindcss/: binary with name "tailwindcss" not found using npx
> Total in 508 ms
> Error: error building site: render: failed to render pages: render of "/home/jmooring/code/hugo-docker-test/content/other-tests/tailwindcss.md" failed: "/home/jmooring/code/hugo-docker-test/layouts/_default/tailwindcss.html:16:23": execute of template failed: template: _default/tailwindcss.html:16:23: executing "main" at <.Content>: error calling Content: TAILWINDCSS: failed to transform "/temp/css" (application/octet-stream). You need to install TailwindCSS CLI. See https://gohugo.io/functions/css/tailwindcss/: binary with name "tailwindcss" not found using npx
> 
> jmooring@ubuntu:~/code/hugo-docker-test (main)$ tailwindcss -v
> ≈ tailwindcss v4.0.0-beta.8
> ```

Now you nailed the problem.
Note that even if we fix this, you cannot use the Hugo snap if you use the Tailwind standalone CLI.
> Note that even if we fix this, you cannot use the Hugo snap if you use the Tailwind standalone CLI.

I've already ditch the snap. I'm using the binary from the release page, see:

```sh
hugo v0.140.2-aae02ca612a02e085c08366a9c9279f4abb39d94+extended linux/amd64 BuildDate=2024-12-30T15:01:53Z VendorInfo=gohugoio
```
I haven't read the latest comments, but I do agree that this behaviour is ... annoying, so I'm fixing it. 

Fix patch:
diff --git a/common/hexec/exec.go b/common/hexec/exec.go
index 4f23d20f505..1369e847cde 100644
--- a/common/hexec/exec.go
+++ b/common/hexec/exec.go
@@ -26,7 +26,10 @@ import (
 	"strings"
 	"sync"
 
+	"github.com/bep/logg"
 	"github.com/cli/safeexec"
+	"github.com/gohugoio/hugo/common/loggers"
+	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/config/security"
 )
@@ -86,7 +89,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
 }
 
 // New creates a new Exec using the provided security config.
-func New(cfg security.Config, workingDir string) *Exec {
+func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
 	var baseEnviron []string
 	for _, v := range os.Environ() {
 		k, _ := config.SplitEnvVar(v)
@@ -96,9 +99,11 @@ func New(cfg security.Config, workingDir string) *Exec {
 	}
 
 	return &Exec{
-		sc:          cfg,
-		workingDir:  workingDir,
-		baseEnviron: baseEnviron,
+		sc:                cfg,
+		workingDir:        workingDir,
+		infol:             log.InfoCommand("exec"),
+		baseEnviron:       baseEnviron,
+		newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
 	}
 }
 
@@ -124,12 +129,14 @@ func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
 type Exec struct {
 	sc         security.Config
 	workingDir string
+	infol      logg.LevelLogger
 
 	// os.Environ filtered by the Exec.OsEnviron whitelist filter.
 	baseEnviron []string
 
-	npxInit      sync.Once
-	npxAvailable bool
+	newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
+	npxInit           sync.Once
+	npxAvailable      bool
 }
 
 func (e *Exec) New(name string, arg ...any) (Runner, error) {
@@ -155,25 +162,86 @@ func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner,
 	return cm.command(arg...)
 }
 
+type binaryLocation int
+
+func (b binaryLocation) String() string {
+	switch b {
+	case binaryLocationNodeModules:
+		return "node_modules/.bin"
+	case binaryLocationNpx:
+		return "npx"
+	case binaryLocationPath:
+		return "PATH"
+	}
+	return "unknown"
+}
+
+const (
+	binaryLocationNodeModules binaryLocation = iota + 1
+	binaryLocationNpx
+	binaryLocationPath
+)
+
 // Npx will in order:
 // 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
 // 2. If not found, and npx is available, run npx --no-install <name> <args>.
 // 3. Fall back to the PATH.
+// If name is "tailwindcss", we will try the PATH as the second option.
 func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
-	// npx is slow, so first try the common case.
-	nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
-	_, err := safeexec.LookPath(nodeBinFilename)
-	if err == nil {
-		return e.new(name, nodeBinFilename, arg...)
+	if err := e.sc.CheckAllowedExec(name); err != nil {
+		return nil, err
 	}
-	e.checkNpx()
-	if e.npxAvailable {
-		r, err := e.npx(name, arg...)
-		if err == nil {
-			return r, nil
+
+	newRunner, err := e.newNPXRunnerCache.GetOrCreate(name, func() (func(...any) (Runner, error), error) {
+		type tryFunc func() func(...any) (Runner, error)
+		tryFuncs := map[binaryLocation]tryFunc{
+			binaryLocationNodeModules: func() func(...any) (Runner, error) {
+				nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
+				_, err := safeexec.LookPath(nodeBinFilename)
+				if err != nil {
+					return nil
+				}
+				return func(arg2 ...any) (Runner, error) {
+					return e.new(name, nodeBinFilename, arg2...)
+				}
+			},
+			binaryLocationNpx: func() func(...any) (Runner, error) {
+				e.checkNpx()
+				if !e.npxAvailable {
+					return nil
+				}
+				return func(arg2 ...any) (Runner, error) {
+					return e.npx(name, arg2...)
+				}
+			},
+			binaryLocationPath: func() func(...any) (Runner, error) {
+				if _, err := safeexec.LookPath(name); err != nil {
+					return nil
+				}
+				return func(arg2 ...any) (Runner, error) {
+					return e.New(name, arg2...)
+				}
+			},
+		}
+
+		locations := []binaryLocation{binaryLocationNodeModules, binaryLocationNpx, binaryLocationPath}
+		if name == "tailwindcss" {
+			// See https://github.com/gohugoio/hugo/issues/13221#issuecomment-2574801253
+			locations = []binaryLocation{binaryLocationNodeModules, binaryLocationPath, binaryLocationNpx}
 		}
+		for _, loc := range locations {
+			if f := tryFuncs[loc](); f != nil {
+				e.infol.Logf("resolve %q using %s", name, loc)
+				return f, nil
+			}
+		}
+		return nil, &NotFoundError{name: name, method: fmt.Sprintf("in %s", locations[len(locations)-1])}
+	})
+	if err != nil {
+		return nil, err
 	}
-	return e.New(name, arg...)
+
+	return newRunner(arg...)
 }
 
 const (
diff --git a/config/allconfig/load.go b/config/allconfig/load.go
index 16e2019cfa9..999e03645b0 100644
--- a/config/allconfig/load.go
+++ b/config/allconfig/load.go
@@ -470,7 +470,7 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
 		ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
 	}
 
-	ex := hexec.New(conf.Security, workingDir)
+	ex := hexec.New(conf.Security, workingDir, l.Logger)
 
 	hook := func(m *modules.ModulesConfig) error {
 		for _, tc := range m.AllModules {
diff --git a/deps/deps.go b/deps/deps.go
index 56a3d36446a..34c41012cf3 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -188,7 +188,7 @@ func (d *Deps) Init() error {
 	}
 
 	if d.ExecHelper == nil {
-		d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir())
+		d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir(), d.Log)
 	}
 
 	if d.MemCache == nil {


Test patch:
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 7a6c040b16a..1880aa62ff2 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -729,7 +729,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 			sc := security.DefaultConfig
 			sc.Exec.Allow, err = security.NewWhitelist("npm")
 			s.Assert(err, qt.IsNil)
-			ex := hexec.New(sc, s.Cfg.WorkingDir)
+			ex := hexec.New(sc, s.Cfg.WorkingDir, loggers.NewDefault())
 			command, err := ex.New("npm", "install")
 			s.Assert(err, qt.IsNil)
 			s.Assert(command.Run(), qt.IsNil)
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index 08eb2178756..5c4e07498ea 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -838,7 +838,7 @@ func (s *sitesBuilder) NpmInstall() hexec.Runner {
 	var err error
 	sc.Exec.Allow, err = security.NewWhitelist("npm")
 	s.Assert(err, qt.IsNil)
-	ex := hexec.New(sc, s.workingDir)
+	ex := hexec.New(sc, s.workingDir, loggers.NewDefault())
 	command, err := ex.New("npm", "install")
 	s.Assert(err, qt.IsNil)
 	return command
diff --git a/markup/asciidocext/convert_test.go b/markup/asciidocext/convert_test.go
index b3f63b4d8a4..e93cab00bb8 100644
--- a/markup/asciidocext/convert_test.go
+++ b/markup/asciidocext/convert_test.go
@@ -313,7 +313,7 @@ allow = ['asciidoctor']
 		converter.ProviderConfig{
 			Logger: loggers.NewDefault(),
 			Conf:   conf,
-			Exec:   hexec.New(securityConfig, ""),
+			Exec:   hexec.New(securityConfig, "", loggers.NewDefault()),
 		},
 	)
 	c.Assert(err, qt.IsNil)
diff --git a/markup/pandoc/convert_test.go b/markup/pandoc/convert_test.go
index 8beedc1150c..dff6b1ed37a 100644
--- a/markup/pandoc/convert_test.go
+++ b/markup/pandoc/convert_test.go
@@ -34,7 +34,7 @@ func TestConvert(t *testing.T) {
 	var err error
 	sc.Exec.Allow, err = security.NewWhitelist("pandoc")
 	c.Assert(err, qt.IsNil)
-	p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc, ""), Logger: loggers.NewDefault()})
+	p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc, "", loggers.NewDefault()), Logger: loggers.NewDefault()})
 	c.Assert(err, qt.IsNil)
 	conv, err := p.New(converter.DocumentContext{})
 	c.Assert(err, qt.IsNil)
diff --git a/markup/rst/convert_test.go b/markup/rst/convert_test.go
index 182858263b8..730e00acf86 100644
--- a/markup/rst/convert_test.go
+++ b/markup/rst/convert_test.go
@@ -36,7 +36,7 @@ func TestConvert(t *testing.T) {
 	p, err := Provider.New(
 		converter.ProviderConfig{
 			Logger: loggers.NewDefault(),
-			Exec:   hexec.New(sc, ""),
+			Exec:   hexec.New(sc, "", loggers.NewDefault()),
 		})
 	c.Assert(err, qt.IsNil)
 	conv, err := p.New(converter.DocumentContext{})
diff --git a/modules/client_test.go b/modules/client_test.go
index 0ee7e0dbc40..6320e33385d 100644
--- a/modules/client_test.go
+++ b/modules/client_test.go
@@ -22,6 +22,7 @@ import (
 	"testing"
 
 	"github.com/gohugoio/hugo/common/hexec"
+	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/config/security"
 	"github.com/gohugoio/hugo/hugofs/glob"
 
@@ -61,7 +62,7 @@ github.com/gohugoio/hugoTestModules1_darwin/modh2_2@v1.4.0 github.com/gohugoio/h
 			WorkingDir: workingDir,
 			ThemesDir:  themesDir,
 			PublishDir: publishDir,
-			Exec:       hexec.New(security.DefaultConfig, ""),
+			Exec:       hexec.New(security.DefaultConfig, "", loggers.NewDefault()),
 		}
 
 		withConfig(&ccfg)