# What Is the aqua exec Command? Purpose and Implementation in Aqua

> Discover the aqua exec command purpose. Learn how aqua exec installs and runs tools managed by Aqua for efficient project workflows.

- Repository: [aquaproj/aqua](https://github.com/aquaproj/aqua)
- Tags: deep-dive
- Published: 2026-02-25

---

**The `aqua exec` command is an internal utility used by aqua-proxy to locate, lazily install if necessary, and execute tools managed by the Aqua package manager.**

The `aqua exec` command serves as the execution bridge in the [aquaproj/aqua](https://github.com/aquaproj/aqua) ecosystem. While end users typically interact with Aqua through simple command calls like `gh version`, the actual process of finding the binary in Aqua's cache, verifying policies, and spawning the process is handled internally by this command.

## Understanding the aqua exec Command Architecture

The `aqua exec` command operates as the runtime engine that translates user tool invocations into actual binary execution. When a user runs an executable that Aqua has downloaded, the **aqua-proxy** wrapper invokes `aqua exec` behind the scenes to handle the complex orchestration.

This command performs five critical architectural steps: parsing CLI arguments, initializing the execution controller, discovering the tool location, optionally installing missing binaries, and finally spawning the process with retry logic.

## How the aqua exec command Works: Step-by-Step Execution Flow

The command follows a strict pipeline defined in the Aqua source code to ensure tools are discovered and executed correctly.

### CLI Entry Point and Argument Parsing

In [`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go) (lines 73-95), the command definition parses the tool name and its arguments, builds a runtime `Param` structure, and initializes optional CPU profiling. The `command.action` function reads arguments, creates a `config.Param`, and calls the controller to begin execution.

### Controller Initialization

The `controller.InitializeExecCommandController` function creates the execution controller with all required dependencies, including the policy reader, which resolver, and installer. This initialization occurs immediately after argument parsing in the CLI layer.

### Tool Discovery via which Service

In [`pkg/controller/exec/exec.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/exec/exec.go) (lines 41-48), the controller invokes `c.which.Which` to locate the requested binary within the local Aqua cache. This step determines whether the tool is already available or needs to be installed.

### Lazy Installation and Policy Enforcement

If the binary is not present, lines 65-78 in [`pkg/controller/exec/exec.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/exec/exec.go) trigger `c.install` to perform on-demand package installation. This process respects policy files and checksum validation before proceeding with execution.

### Process Execution with Retry Logic

Finally, `c.execCommandWithRetry` (lines 54-66) handles the actual process spawning using the `osexec` wrapper, or a low-level `execve(2)` call when enabled. The controller implements retry logic if the process fails to start, ensuring robust execution.

## Practical Examples of Using the aqua exec command

While primarily an internal mechanism, understanding how to interact with this command helps with debugging and advanced scripting.

### Automatic Usage via aqua-proxy

When you run a tool managed by Aqua, such as `gh version`, the `aqua-proxy` wrapper automatically invokes the execution command behind the scenes:

```bash

# What happens internally when you type:

$ gh version

# aqua-proxy executes:

$ aqua exec -- gh version

```

This transparent handling ensures users can run tools without knowing their exact installation paths in the Aqua cache.

### Manual Invocation for Debugging

Developers can call the command directly to troubleshoot tool resolution or verify which binary Aqua selects:

```bash

# Directly execute a specific tool through Aqua

$ aqua exec -- gh version

# This bypasses the proxy and shows any resolution errors directly

```

### Programmatic Usage in Go

The controller can be initialized and invoked programmatically, as implemented in the CLI:

```go
import (
    "context"
    "net/http"
    "github.com/aquaproj/aqua/v2/pkg/config"
    "github.com/aquaproj/aqua/v2/pkg/controller"
    "github.com/aquaproj/aqua/v2/pkg/runtime"
)

func runTool() error {
    ctx := context.Background()
    param := &config.Param{}
    
    // Initialize the execution controller
    ctrl, err := controller.InitializeExecCommandController(
        ctx,
        nil, // logger
        param,
        http.DefaultClient,
        runtime.New(),
    )
    if err != nil {
        return err
    }

    // Execute "gh version"
    return ctrl.Exec(ctx, nil, param, "gh", "version")
}

```

The `Exec` method implements the complete workflow from discovery to process spawning.

## Key Source Files and Implementation Details

The execution logic spans several packages within the `aquaproj/aqua` repository:

- **[`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go)** – Defines the CLI interface, parses arguments, and bootstraps the controller.
- **[`pkg/controller/exec/exec.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/exec/exec.go)** – Contains the core `Exec` controller that orchestrates tool discovery, lazy installation, and process execution.
- **[`pkg/controller/which/which.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/which/which.go)** – Implements the resolution logic that locates binaries within the Aqua cache.
- **[`pkg/installpackage/installer.go`](https://github.com/aquaproj/aqua/blob/main/pkg/installpackage/installer.go)** – Handles on-demand package installation when tools are not yet cached.
- **[`website/docs/reference/execve-2.md`](https://github.com/aquaproj/aqua/blob/main/website/docs/reference/execve-2.md)** – Documents the low-level `execve(2)` fallback mechanism used during process spawning.

These files collectively implement the bridge between Aqua's declarative package management and actual tool execution.

## Summary

- The **`aqua exec` command** is an internal utility that translates user tool invocations into actual binary execution within the Aqua ecosystem.
- It operates as the **execution engine** behind `aqua-proxy`, handling tool discovery, lazy installation, and process spawning.
- The command implements a **five-stage pipeline**: CLI parsing, controller initialization, tool discovery via `which`, optional lazy installation, and execution with retry logic.
- Key implementation files include [`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go) for the CLI layer and [`pkg/controller/exec/exec.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/exec/exec.go) for the core execution logic.
- While primarily used internally, the command can be invoked manually for debugging or accessed programmatically via the Go controller interface.

## Frequently Asked Questions

### Is aqua exec meant to be run directly by users?

No, `aqua exec` is primarily an internal command used by `aqua-proxy`. End users typically run tools directly (e.g., `gh version`), and the proxy automatically invokes `aqua exec` behind the scenes to handle the complex orchestration of finding and executing the correct binary. However, advanced users can invoke it manually for debugging purposes.

### How does aqua exec handle missing tools?

The command implements **lazy installation** through the controller's installation logic. When `c.which.Which` fails to locate a binary in the Aqua cache, the controller triggers `c.install` to download and install the package on-demand. This process respects policy files and performs checksum validation before making the binary available for execution.

### What is the relationship between aqua exec and aqua-proxy?

`aqua-proxy` acts as a lightweight wrapper that intercepts tool calls and delegates the actual execution work to `aqua exec`. When you run a managed tool like `gh`, the proxy invokes `aqua exec -- gh <args>` to handle the complex logic of finding the correct version in the cache, installing if necessary, and spawning the process.

### Where is the execution logic implemented in the source code?

The core execution logic resides in [`pkg/controller/exec/exec.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/exec/exec.go), specifically within the `Exec` method and helper functions like `execCommandWithRetry`. The CLI entry point and controller initialization are defined in [`pkg/cli/exec/command.go`](https://github.com/aquaproj/aqua/blob/main/pkg/cli/exec/command.go), while tool resolution happens in [`pkg/controller/which/which.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/which/which.go) and installation logic in [`pkg/installpackage/installer.go`](https://github.com/aquaproj/aqua/blob/main/pkg/installpackage/installer.go).