TL;DR: `execute` takes a LIST, not a shell string. `execute = ["ls -la"]` looks for a binary literally named `ls -la` and fails with "executable file not found". Split it into `["ls", "-la"]`, or use `["bash", "-c", "..."]` when you genuinely need a shell.

```text
Error: hook "preflight" failed: exec: "ls -la": executable file not found in $PATH
```

## Steps

1. Look at the `execute` attribute: is it a single string with spaces?
   Expected: you find `execute = ["ls -la"]` or similar.
2. Split it:
   ```hcl
   terraform {
     before_hook "preflight" {
       commands = ["plan"]
       execute  = ["bash", "-c", "set -euo pipefail; ./scripts/preflight.sh"]
     }
   }
   ```
   Expected: binary and args are separate list elements.
3. If the hook fails silently, remove `suppress_stdout = true` while debugging (it hides the hook's stdout, where diagnostics usually go).
   Expected: you can see the hook's actual output.
4. Re-run.
   Expected: the hook executes.

## When this applies

- Hook fails with `executable file not found in $PATH` naming a string with spaces.
- A command that works in your shell fails as a hook.

## When it doesn't apply

- The hook RUNS but exits non-zero: that's the script's own failure; read its output (check `suppress_stdout` first).
- `commands` doesn't list the subcommand you're running: the hook never fires at all, which looks different (no error).

## Tool versions

All Terragrunt versions (verified on 1.1.3).

## Why it happens

Terragrunt execs the list directly without a shell, so the first element must be a real binary. A single string is treated as one binary name, spaces included. There is no shell splitting, globbing, or piping unless you invoke one explicitly.

## Edge cases

- Hooks run from the module directory by default (a temp `.terragrunt-cache` copy for remote sources), NOT the config directory; anchor scripts with `get_terragrunt_dir()` instead of relative paths.
- Each `execute` is its own process: a `TF_VAR_*` exported by one hook is not visible to the next.