Table of Contents

Your first program

This program creates a sandbox, waits until it is ready, runs a command in it, prints the output, and deletes the sandbox again.

1. Create the project

dotnet new console -n HelloOpenShell
cd HelloOpenShell
dotnet add package OpenShell.Net

2. Write the program

Replace the contents of Program.cs. Start with the namespaces and the client; this connects to the active gateway registered by the OpenShell CLI. To use an explicit address and token instead, create the client as shown in Connect to a gateway.

using OpenShell;
using OpenShell.Gateway;
using var client = GatewayClient.Connect();

Then add the program itself below it:

// 1. Create the sandbox. The name is optional: pass null to let the gateway generate one.
var sandbox = await client.Sandboxes.CreateAsync(
    "hello-openshell",
    new SandboxSpec { Image = "registry.example.com/team/python-agent:1.0" });

try
{
    // 2. Wait until it is Ready. A sandbox that fails, or a timeout, throws OpenShellException.
    await client.Sandboxes.WaitReadyAsync(sandbox.Name, new WaitOptions { Timeout = TimeSpan.FromMinutes(2) });

    // 3. Run a command and read its output.
    var result = await client.Exec.RunAsync(sandbox.Name, ["echo", "hello from OpenShell"]);
    Console.WriteLine($"exit {result.ExitCode}: {result.StdoutText}");
}
finally
{
    // 4. Delete it, and wait while the gateway finishes the cleanup in the background.
    var deletion = await client.Sandboxes.DeleteAsync(sandbox.Name);
    if (deletion.Outcome == DeletionOutcome.Accepted)
    {
        await client.Sandboxes.WaitDeletedAsync(
            sandbox.Name,
            new WaitOptions { ExpectedSandboxId = deletion.ResourceId });
    }
}

Use an image your gateway can pull.

3. Run it

dotnet run
exit 0: hello from OpenShell

What happened

  1. CreateAsync asked the gateway for a sandbox and returned as soon as the gateway accepted it, in the Provisioning phase.
  2. WaitReadyAsync polled the sandbox, with backoff, until it reached Ready. A sandbox that enters Error or stops fails the wait at once; running out of time throws an OpenShellException whose Kind is DeadlineExceeded.
  3. RunAsync ran the command (an argument list, not a shell string) and buffered its output into an ExecResult: ExitCode, StdoutText, StderrText. A non-zero exit code is a result, not an exception.
  4. DeleteAsync returned a typed DeletionResult. Accepted means the gateway is still cleaning up, so the program waits with WaitDeletedAsync; ExpectedSandboxId keeps a new sandbox with the same name from extending the wait.

Every failure surfaces as an OpenShellException with a Kind you can switch on; see Errors.

Tip

The same code runs unchanged against FakeOpenShellClient from OpenShell.Net.Testing, which is how this documentation tests it. See Testing.

Next steps

  • Sandboxes: images, environment, labels, templates, stop and start, suspend and resume, updates.
  • Exec and streaming: streaming output, interactive sessions, logs, and watches.
  • Authentication: OIDC, client credentials, mTLS, and Cloudflare Access.