jj / How jj util exec works

How jj util exec works

jj util exec runs another program and exits with its exit code, so an alias can run any script as a jj subcommand.

On this page
  1. 1. Command-line surface
  2. 3. End-to-end flow
  3. 4. Core architecture touchpoints

In short: it starts the program with the given arguments, adding JJ_WORKSPACE_ROOT to its environment when a workspace was found, waits for it, and exits with the same code. It never loads the repo and never snapshots. Upstream's help warns that it is "a convenient syntax for running arbitrary code on your system" (source).

The command is cmd_util_exec. Its help text: "Execute an external command via jj" (source). It is the Exec variant of UtilCommand (source, dispatch).

1. Command-line surface

ArgumentTypeDefaultMeaning
COMMAND (positional, required)stringnone"External command to execute" (source).
ARGS… (positional)stringsnone"Arguments to pass to the external command" (source).

The intended use is an alias such as my-script = ["util", "exec", "--", "my-jj-script"]; the -- makes jj pass the script's flags through instead of parsing them (source). The global flags are on the CLI page.

3. End-to-end flow

jj util exec: call flow cmd_util_exec asks for the workspace loader and, if there is one, takes its workspace root. It builds a process for COMMAND with ARGS, setting JJ_WORKSPACE_ROOT if a root was found, and waits for it. If the program cannot be started, the error is "Failed to execute external command". If it exits with a code, jj exits with that code. If it was killed by a signal, the error is "External command was terminated by" the signal. Command::new(COMMAND).args(ARGS) + JJ_WORKSPACE_ROOT if a workspace status(): run and wait can't start → "Failed to execute …" exit code process::exit(code) signal "terminated by …"

4. Core architecture touchpoints

PieceRole here
CommandHelper::workspace_loaderOnly used to find the workspace root; an error just means no JJ_WORKSPACE_ROOT (source, test).
The process exitA child's exit code becomes jj's own through std::process::exit, skipping jj's usual error reporting. So a child's clap error exits 2 with only the child's message (test).
Repo, working copy, operation logNot touched by jj itself; the child may run jj, which then snapshots as usual.