> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Exec

> Execute explicit actions with guards, bounded processes, and optional refresh policy.

Prefer a declarative resource when it already owns the desired state. Use exec for explicit work with an author-defined convergence policy.

```hcl
exec "initialize" {
  command = ["/usr/bin/touch", "/var/lib/rivet-example/initialized"]
  creates = ["/var/lib/rivet-example/initialized"]
  cwd     = "/"
  umask   = "0022"
}
```

The parent directory must already exist or be declared as a prerequisite.

| Attribute            | Default                      | Behavior                                                                              |
| -------------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
| `command`            | One command form required    | Nonempty argv list; literal argument boundaries, no shell expansion.                  |
| `shell_command`      | Alternative form             | String evaluated by the explicit shell.                                               |
| `shell`              | `/bin/sh` for shell commands | Only valid with `shell_command`.                                                      |
| `cwd`                | `/`                          | Absolute working directory.                                                           |
| `user`, `group`      | Runner identity              | Account names or numeric IDs.                                                         |
| `environment`        | Empty additions              | Map of strings added to or overriding fixed PATH and C locale.                        |
| `stdin`              | Empty                        | String supplied to command stdin.                                                     |
| `umask`              | `"0022"`                     | Octal string.                                                                         |
| `creates`            | Empty                        | Absolute paths; skip if all exist, verify after execution.                            |
| `only_if`            | Empty                        | List of argv probes; all must return zero to run.                                     |
| `unless`             | Empty                        | List of argv probes; any zero result skips work.                                      |
| `refresh_only`       | `false`                      | Run only when a notification is delivered.                                            |
| `always`             | `false`                      | Explicit permission to run on each apply; cannot combine with guards or refresh-only. |
| `success_exit_codes` | `[0]`                        | Accepted exit codes.                                                                  |
| `changed_exit_codes` | All accepted codes           | If supplied, must be a subset of success codes.                                       |

Exactly one of `command` and `shell_command` is required. At least one nonempty guard policy, `refresh_only = true`, or `always = true` is required. Empty lists alone do not supply a policy. Nullable optional scalar fields and exit-code lists use their default and unmanaged rules; guards and environment require actual lists/maps.

## Guards

```hcl
exec "check_policy" {
  command = ["/usr/bin/true"]
  only_if = [["/usr/bin/test", "-d", "/etc"]]
  unless  = [["/usr/bin/test", "-e", "/etc/app-disabled"]]
  changed_exit_codes = []
}
```

Guards run in the same execution context with empty stdin. Normal nonzero probe exits are boolean results; launch errors, signals, unreadable paths, and timeouts are failures. Guards should be read-only. They are not sandboxed.

Accepted unchanged exits ran the command but do not trigger notifications. Successful ordinary execs are not automatically retried.

## Refresh-only work

```hcl
file "config" {
  path    = "/etc/rivet-message"
  content = "managed\n"
  meta { notify = [exec.refresh] }
}

exec "refresh" {
  command      = ["/usr/bin/true"]
  refresh_only = true
}
```

Use the real application-specific command in place of `/usr/bin/true`. Guards are evaluated again at delivery. Coalesced refresh obligations may be delivered again after a crash.

## Process behavior

The default shared resource budget is five minutes. The runner drains both output streams while supplying stdin, captures at most 512 KiB per stream, and continues draining excess output. Successful nonsensitive output appears in JSON and final stored reports with a truncation flag. Sensitive, failed, validator, probe, and systemd output is discarded.

Identity setup uses trusted `setpriv`, initializes supplementary groups, and uses NSS for primary-group lookup. There is no automatic account dependency from a literal identity string; add explicit ordering when needed.

Timeout cancellation sends TERM to the owned process group, then KILL after a one-second grace period and reaps the leader. Escaped descendants or uninterruptible kernel calls prevent a hard termination guarantee.

After interruption, execs re-evaluate their guards. An `always = true` command runs again on the next apply, and refresh-only commands can repeat after interrupted delivery. No manual journal acknowledgement is required; configure guards when repeated side effects must be avoided. See [recovery](/reference/recovery). Capability: `exec.process.v1`.