Skip to content

Non-interactive use

The shell hook exists to change your interactive shell — that's why envoke shell-hook prints shell code for your shell to eval/source rather than running anything itself. Scripts, Makefiles and CI jobs have no interactive shell to hook into, so they get a separate entry point:

envoke exec [<from> [<to>]]

envoke exec resolves the same blocks the shell hook would for that directory change and runs each one in its own sh -c subprocess, with the matched directory as the working directory and the same ENVOKE_* variables set.

envoke exec "$PWD" ~/Projects/my-app

Both arguments are optional and may be relative, on exactly the same terms as envoke debug: <to> defaults to the directory you are in, and <from> to $OLDPWD. On PowerShell and tcsh, name <from> yourself — neither maintains a usable $OLDPWD.

A single argument is <from>, with <to> inferred, which is the form every shell can type:

cd ~/Projects/my-app
envoke exec ~/Projects

For that form alone, exec writes the pair it resolved to stderr:

envoke exec: /home/you/Projects -> /home/you/Projects/my-app

envoke exec <dir> can be read as "run the blocks for this directory", which would be the opposite direction — that directory's leave blocks, not its enter ones — so the line makes the misreading visible. The no-argument and two-argument forms print nothing extra, so no invocation that already worked changes what it writes. It goes to stderr because stdout belongs to the blocks, and a caller capturing them must not collect a diagnostic.

Unix only

Blocks run through sh -c, so envoke exec needs a POSIX shell on PATH. Windows does not provide one by default, and envoke exec says so rather than reporting a missing program you never asked for:

envoke: enter ./api (C:\src\api:3): no POSIX shell ("sh") on PATH
envoke: exec runs each block through `sh`; install a POSIX shell (Git for Windows, MSYS2 or WSL each provide one) or use the shell hook, which runs blocks in the shell you already have

The shell hook is unaffected — the PowerShell hook renders into PowerShell itself, and needs no sh.

What it does not do

Side effects stay in the subprocess. export, source and cd inside a block affect that block's own subprocess and nothing else — they cannot reach the shell that invoked envoke exec, or any later command in your script. That is not a limitation to work around; it is what "subprocess" means. If you want a block's export to be visible afterwards, you need the shell hook and an interactive shell, or you need the block to write something your script then reads.

So envoke exec is for blocks whose value is their effect — writing a file, warming a cache, starting a service, running a code generator — not for blocks whose value is the environment they leave behind.

Trust applies exactly as it does everywhere else

envoke exec refuses to run anything from a config that has not been through envoke allow since its last edit:

envoke: /home/you/.envokerc: config is not trusted
envoke: approve a config with `envoke allow` before it will run here

It exits 1 in that case, and 1 as soon as any block exits non-zero — remaining blocks are not run, and nothing is unwound (see the enter/leave independence rule in Configuration).

Nothing prompts, here or anywhere else — approve configs beforehand, in your provisioning step, where --yes skips the interactive prompt:

envoke allow --yes ./ci/envokerc

envoke exec is also the only way to run blocks non-interactively: the generated shell hooks refuse to install themselves in a non-interactive shell, so a script that cds does not run your enter/leave blocks by accident.

With several configs in play, one that is untrusted or unparseable does not stop the others: it is reported on stderr, the trusted ones still run, and the exit code is 1 to say something was skipped. One fragment a git pull just rewrote must not silently disable the config you did approve.

That stop-on-failure behaviour is specific to envoke exec. The shell hook does the opposite: it hands your shell every matched block at once, so a failing one doesn't stop the rest. See When a block fails.

envoke disable applies here too, and envoke exec says so rather than silently doing nothing:

envoke: disabled by the persistent switch -- no blocks were run

It still exits 0 — being switched off is what was asked for, not a failure. Set ENVOKE_DISABLE=0 for a job that must run its blocks regardless.

Interruption

A SIGINT or SIGTERM interrupts the running block rather than killing envoke out from under it, so a trap in the block gets a chance to clean up; it is killed five seconds later if it hasn't exited. envoke exec then exits 130.

Seeing what would run first

envoke debug [<from> [<to>]] prints the same resolution without executing anything, trusted or not. It is the right thing to reach for when a job's blocks did not do what you expected.