OpenShell CLI

SkillAI & models

Lets your agent manage OpenShell sandboxes, providers, gateways, and policies through the openshell command-line tool.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the OpenShell CLI skill

About this capability

Guide agents through using the OpenShell CLI (openshell) for sandbox management, gateway registration, provider configuration and refresh, policy iteration, settings, service exposure, BYOC workflows, and attached-provider inference. Covers basic through advanced multi-step workflows. Trigger keywor

What this skill tells your AI

The instructions your AI receives, as published by nvidia/openshell in skills/openshell-cli/SKILL.md and read by ahel’s review.

Guide agents through using the openshell CLI for sandbox and platform management -- from basic operations to advanced multi-step workflows.

Overview

The OpenShell CLI (openshell) is the primary interface for managing sandboxes, providers, policies, settings, exposed services, and gateway registrations. Gateway service lifecycle is handled outside the CLI by packages, systemd, or Helm. This skill teaches agents how to orchestrate CLI commands for common and complex workflows.

Companion skill: For creating or modifying sandbox policy YAML content (network rules, L7 inspection, access presets), use the generate-sandbox-policy skill. This skill covers the CLI commands for the policy lifecycle; generate-sandbox-policy covers policy content authoring.

Self-teaching: The CLI has comprehensive built-in help. When you encounter a command or option not covered in this skill, walk the help tree:

openshell --help                    # Top-level commands
openshell <group> --help            # Subcommands in a group
openshell <group> <cmd> --help      # Flags for a specific command

This is your primary fallback. Use it freely -- the CLI's help output is authoritative and always up-to-date.

Prerequisites

  • openshell is on the PATH. Follow the published installation guide when it is not installed.
  • A reachable OpenShell gateway backed by Docker, Podman, Kubernetes, or the experimental VM driver
  • Docker is running only when using BYOC local builds or a Docker-backed gateway
  • For Kubernetes deployments: kubectl and Helm access to the target cluster

Authoritative References

Use openshell --help and nested --help output as the authority for the installed CLI version. Use the published documentation for product concepts and supported workflows:


Workflow 1: Getting Started

Use this workflow when the user has a gateway endpoint and wants to get a sandbox running for the first time.

Step 1: Register a gateway

openshell gateway add http://127.0.0.1:8080 --local --name local

Use an http:// endpoint only for trusted local port-forwarding or a protected private path. For a gateway behind an authenticated reverse proxy, register its HTTPS endpoint with openshell gateway add https://gateway.example.com.

Step 2: Verify the gateway

openshell status
openshell whoami

Confirm the gateway is reachable, authentication is valid or not required, and the output shows a version. Status: Connected only proves the public health endpoint is reachable; inspect the separate Authentication line before running protected commands. openshell whoami reports the identity validated by the gateway, including the subject an administrator uses for workspace membership. Add --output json for automation.

Step 3: Create a sandbox

The simplest way to get a sandbox running:

openshell sandbox create

This creates a sandbox whose canonical main process is /bin/bash -l and attaches your terminal to that retained process. Add --detach to return after the sandbox becomes ready without attaching.

An explicit trailing command is foreground even when stdin or stdout is not a terminal. The CLI streams its stdout and stderr and returns its exact exit status. Exit code 0 leaves a retained sandbox in Completed; nonzero leaves it in Error with MainProcessFailed. Use --no-keep to delete either result after output drains, or --detach for a long-running service. Combine --detach --no-keep when the gateway should run the service without a host attachment and delete its sandbox after the service exits.

When supplying --name, use a portable DNS-1123 label: at most 63 lowercase alphanumeric or - characters, beginning and ending with an alphanumeric character. The Kubernetes driver rejects uppercase letters, underscores, dots, and other names that cannot become Kubernetes resource labels.

Provider attachment is explicit. Name each provider with --provider; the trailing command does not select or attach one. If the named provider does not exist but a profile with that ID is available, the CLI can create it from local credentials:

openshell sandbox create --provider claude-code -- claude
openshell sandbox create --provider codex -- codex

The agent will be prompted interactively if credentials are missing.

Step 4: Exit and clean up

Exit the sandbox shell (exit or Ctrl-D), then:

openshell sandbox delete <name>

Workflow 2: Provider Management

Providers supply credentials and provider-specific configuration to sandboxes. Provider profiles are import-only: a gateway serves exactly what an operator imported, and a new gateway serves an empty catalog. Never rely on a hard-coded type list or on a legacy alias such as gh or claude--type matches a profile ID exactly. Discover the profiles available on the selected gateway:

openshell provider list-profiles
openshell provider list-profiles --output json

Create a provider from local credentials

openshell provider create --name my-github --type github --from-existing

The --from-existing flag discovers credentials from local state (e.g., gh auth tokens, Claude config files).

Create a provider with explicit credentials

openshell provider create --name my-openai --type openai \
  --credential OPENAI_API_KEY

Bare KEY reads the value from the environment variable of that name and avoids placing the secret in shell history. Use KEY=VALUE only when the user explicitly accepts that exposure.

Other credential sources are --from-gcloud-adc for compatible profiles and --runtime-credentials when the gateway or sandbox resolves the required credentials at runtime.

Static provider credentials resolve only for hosts, ports, and paths declared by the provider profile. Use provider profile export to inspect that boundary when a placeholder is present but requests receive credential_endpoint_mismatch. A profileless static provider fails closed because the gateway cannot construct a binding.

When an inspected request receives request_authority_mismatch, compare its HTTP authority with the CONNECT tunnel endpoint. The host and effective port must match. For a tunnel to api.example.com:8443, send Host: api.example.com:8443; Host: api.example.com omits the non-default port and is rejected. An absolute-form request target must use the same authority.

Profile-backed providers always contribute policy unless a gateway-global policy is active. Static credential endpoint binding remains independently enforced.

Inspect and manage provider profiles

openshell provider profile export github --output yaml
openshell provider profile lint --file ./my-profile.yaml
openshell provider profile import --file ./my-profile.yaml

List, inspect, update, delete

Use openshell sandbox provider status --help and the attach, detach, and update help to find the installed version's wait options. Add --wait when the next step depends on a provider change taking effect. Without it, a successful command only confirms that the gateway saved the change. Save the returned receipt_id to check that same change later, and inspect the result for every selected sandbox. Credential refresh status confirms that OpenShell obtained credentials; provider status confirms that the sandbox applied them, activated the policy, and updated the environment for new processes. If the status is superseded, explain that a later change replaced the request and inspect that change separately.

If attach, detach, or update reports CONFIG_OPERATION_STORAGE_UNCERTAIN, explain that the change may already be saved and its readiness receipt may be unavailable. Do not blindly retry the mutation. Inspect the provider and sandbox state and reconcile the saved change before deciding on another mutation; the error proves neither rollback nor readiness.

openshell provider list
openshell provider list --output json
openshell provider get my-github
openshell provider update my-github --from-existing
openshell provider delete my-github

provider update does not take --type. It updates credentials, config, or credential expiry on the existing provider.

Configure credential refresh

Use refresh commands only when the provider profile and gateway support refreshable credentials:

openshell provider refresh status my-provider
openshell provider refresh configure my-provider \
  --credential-key MS_GRAPH_ACCESS_TOKEN \
  --strategy oauth2-refresh-token \
  --secret-material-env REFRESH_TOKEN=MS_GRAPH_REFRESH_TOKEN \
  --credential-expires-at 2026-07-16T00:00:00Z
openshell provider refresh rotate my-provider --credential-key ACCESS_TOKEN

Prefer --secret-material-env KEY[=ENVVAR] for secret refresh material. --material KEY=VALUE is for non-secret material; --secret-material-key marks supplied material keys as secret.

The gateway stores secret refresh material through its active credential driver. With Vault selected, refresh tokens, client secrets, and private keys live in Vault alongside injectable provider credentials; refresh state contains only opaque handles. A credential-backend read or write failure makes refresh fail closed rather than falling back to inline storage. Before OpenShell 0.1.0, the gateway does not migrate legacy inline refresh material or move secrets between credential backends. Reconfigure affected grants after upgrading, and remove or reconfigure credentials while the original backend remains available before changing backends. Do not run mixed gateway versions against the same refresh records.

Gateway-managed refresh credentials use an identity-stable workload handle. Routine automatic refresh and provider refresh rotate update the access token behind that handle, so long-running processes do not need to restart. Running processes must be restarted once when upgrading from revision-scoped placeholders. A later provider refresh configure call is an explicit reauthorization boundary: it revokes the previous handle, and processes holding that handle fail closed until restarted.

While gateway-managed refresh is configured, provider update --credential cannot replace or delete the refresh-owned primary credential or any co-minted output. Use provider refresh rotate, reconfigure refresh, or delete refresh before returning those keys to manual management. Unrelated provider fields remain updateable.

When OAuth refresh fails, inspect the RECOVERY and FAILURE_CODE columns from provider refresh status; do not infer the remedy from HTTP status or parse LAST_ERROR. retry means the worker will try again, reauthorize means the user must obtain a new OAuth grant and run provider refresh configure, fix_configuration means an operator must repair the OAuth client, scopes, or administrator policy, and investigate means the issuer returned an unrecognized response. The gateway parks reauthorize records until a manual rotate or reconfiguration. It retries fix_configuration records hourly so externally repaired configuration can recover without rapid token-endpoint traffic. The existing access credential remains usable only until its recorded expiry.


Workflow 3: Sandbox Lifecycle

Create with options

openshell sandbox create \
  --name my-sandbox \
  --provider my-github \
  --provider my-claude \
  --policy ./my-policy.yaml \
  --upload .:/workspace \
  --label team=agents \
  -- claude

Key flags:

  • --provider: Attach configured credential providers for API keys, tokens, and other secrets (repeatable)
  • --policy: Custom policy YAML (otherwise uses built-in default or OPENSHELL_SANDBOX_POLICY env var)
  • --gpu [COUNT]: Request the driver's default GPU selection or a specific GPU count
  • --cpu, --memory: Set per-sandbox compute sizing. Docker/Podman apply limits; Kubernetes applies matching requests and limits.
  • --driver-config-json: Pass experimental driver-specific sandbox configuration
  • --template NAME: Create from a named sandbox workload template. Conflicts with inline workload flags such as --from, --gpu, --cpu, --memory, --env, and --driver-config-json.
  • --label KEY=VALUE: Add labels for later selection (repeatable)
  • --env KEY=VALUE: Set non-secret sandbox environment variables (repeatable); use --provider for credentials
  • --tty: Allocate a retained PTY for the canonical main process
  • --approval-mode manual|auto: Control handling of agent-authored policy proposals; manual is the default
  • --upload <PATH>[:<DEST>]: Upload local files into the container working directory or an explicit destination
  • --no-git-ignore: Disable .gitignore filtering for uploads
  • --no-keep: Delete the sandbox after main output and the exit result drain
  • --detach: Start the canonical main process without attaching
  • --forward [BIND_ADDRESS:]PORT: Forward a local port and keep the sandbox alive
  • --editor vscode|cursor: Open a remote editor after creation and keep the sandbox alive

--detach adds no attachment grace period. When the canonical process exits, its terminal phase is reported immediately. A foreground create declares one expected main-process SSH attachment; cleanup finalizes after that connection closes naturally. With --detach --no-keep, the gateway owns the detached process lifecycle and deletes the ephemeral sandbox after terminal reporting finishes.

Do not combine --upload with a trailing main command. Uploads currently finish after the canonical process starts; create a scratch sandbox and use sandbox exec, or build the files into the image.

Create from a reusable workload template when several sandboxes should share image, environment, sizing, or driver-specific configuration:

openshell sandbox template create gpu-kata \
  --image ghcr.io/nvidia/openshell-community/sandboxes/python:latest \
  --cpu 2 \
  --memory 4Gi \
  --gpu 1 \
  --driver-config-json '{"kubernetes":{"pod":{"node_selector":{"pool":"gpu"}}}}'

openshell sandbox create --name my-sandbox --template gpu-kata --provider my-github -- claude

Direct sandbox create --driver-config-json remains valid for one-off creates. Put driver config on a template only when it should be reused.

Manage sandbox workload templates

openshell sandbox template create gpu-kata \
  --image ghcr.io/nvidia/openshell-community/sandboxes/python:latest \
  --cpu 2 \
  --memory 4Gi \
  --gpu 1 \
  --label team=runtime \
  --env FEATURE_FLAG=on
openshell sandbox template list
openshell sandbox template list --label-selector team=runtime
openshell sandbox template list --all-workspaces --output json
openshell sandbox template get gpu-kata
openshell sandbox template delete gpu-kata

Template --image accepts an OCI image reference. If omitted, the gateway applies its default sandbox image when creating a sandbox from the template. Create-time policy, providers, labels, uploads, forwarding, editor launch, and the initial command stay on sandbox create.

List and inspect sandboxes

openshell sandbox list
openshell sandbox list --selector team=agents --output json
openshell sandbox get my-sandbox

Most commands with an optional sandbox name use the last-used sandbox. Pass an explicit name in automation.

Connect to a running sandbox

openshell sandbox connect my-sandbox
openshell sandbox connect my-sandbox --editor vscode

Attaches to the sandbox's existing canonical main process. Disconnecting leaves that process running; reconnecting targets the same process instance and replays recent output. Use sandbox exec --tty -- /bin/bash -l for a new shell. Press Ctrl-P, then Ctrl-Q to disconnect without terminating main. Ctrl-C retains its normal terminal behavior and interrupts the foreground process. Configure VS Code Remote-SSH with:

openshell sandbox ssh-config my-sandbox >> ~/.ssh/config

Upload and download files

# Upload local files to the sandbox working directory
openshell sandbox upload my-sandbox ./src

# Download a path relative to the sandbox working directory
openshell sandbox download my-sandbox output ./local-output

Uploads honor .gitignore by default. Add --no-git-ignore only when ignored files are intentionally in scope.

Uploads preserve symlinks, including dangling symlinks, instead of dereferencing their targets. A symlink source bypasses Git-aware filtering so the link itself is archived.

When the upload destination is omitted, the CLI discovers the remote working directory. Uploading a named directory merges it into an existing directory of the same name, overwriting matching entries without deleting unrelated entries. Downloads accept paths relative to that working directory or absolute paths within it.

Execute a non-interactive command

openshell sandbox exec --name my-sandbox --workdir /workspace -- ls -la
openshell sandbox exec --name my-sandbox --env MODE=test -- cargo test

sandbox exec starts an independent sibling process, streams output, and exits with the remote command's exit code. Use sandbox connect to attach to the canonical main process. Use --env only for non-secret values. Attach credentials to the sandbox with a provider instead of passing API keys, tokens, or other secrets to sandbox exec.

Change attached providers

openshell sandbox provider list my-sandbox
openshell sandbox provider list my-sandbox --output json
openshell sandbox provider attach my-sandbox my-github --wait --timeout 30
openshell sandbox provider status my-sandbox my-github --output json
openshell sandbox provider detach my-sandbox my-github --wait --timeout 30

Structured attachment output contains provider names, types, and sorted credential and config key names. It never contains credential, handle, or config values.

View logs

# Recent logs
openshell logs my-sandbox

# Stream live logs
openshell logs my-sandbox --tail

# Filter by source and level
openshell logs my-sandbox --tail --source sandbox --level warn

# Logs from the last 5 minutes
openshell logs my-sandbox --since 5m

Delete sandboxes

openshell sandbox delete my-sandbox
openshell sandbox delete sandbox-1 sandbox-2 sandbox-3   # Multiple at once
openshell sandbox delete --all

deletion accepted means cleanup is still pending. Inspect the sandbox until it disappears before assuming completion. An already-absent sandbox succeeds; missing workspaces and authorization failures remain errors. Do not blindly retry by name if another process might have recreated that name.

Stop and start sandboxes

Use stop to halt compute while retaining the sandbox and its persistent workspace:

openshell sandbox stop [name]
openshell sandbox start [name]

Both commands default to the last-used sandbox. Stop stops background forwards and waits for Stopped; start waits for Ready. Connect, exec, file transfer, forwarding, and exposed services are unavailable while stopped or completed. Starting a retained Completed or Error/MainProcessFailed sandbox launches a fresh canonical-main instance and invalidates SSH sessions from the previous runtime generation. Delete remains the operation that removes retained state.


Workflow 4: Policy Iteration Loop

This is the most important multi-step workflow. It enables a tight feedback cycle where sandbox policy is refined based on observed activity.

Key concept: Policies have static fields (immutable after activation: filesystem_policy, landlock, process) and two dynamic fields: network_policies and network_middlewares. Both dynamic fields can be updated without recreating the sandbox when the selected compute driver supports live policy updates. Drivers without the standard supervisor fetch revisions through the sandbox configuration API and report whether they loaded them.

If startup reports ConfigurationInvalid, inspect openshell sandbox get and repair the complete policy or provider set through the gateway. The workload has not started on its first activation, so static fields can also be replaced during this initial repair. A previously activated sandbox retains static-field restrictions while restart admission is pending or rejected. Before the gateway's 300-second repair window expires, successful validation completes startup in place. Effective stored configuration changes and their first failed load reset that window; repeated failures do not. After ProvisioningTimedOut, inspect the retained record and cleanup status, repair configuration, and explicitly run sandbox start once cleanup completes. A CLI wait timeout is separate from this gateway deadline. Follow the published policy repair guidance and confirm current replacement/detach syntax with installed CLI help.

An endpoint with omitted protocol retains explicit-proxy behavior. Explicit protocol: tcp requests policy DNS and transparent TCP and currently requires the Docker or Podman runtime; unsupported runtimes reject the policy before starting the workload rather than activating only part of the network contract.

Create sandbox with initial policy
        │
        ▼
   Monitor logs ◄──────────────────┐
        │                          │
        ▼                          │
  Observe denied actions           │
        │                          │
        ▼                          │
  Pull current policy              │
        │                          │
        ▼                          │
  Modify policy YAML               │
  (use generate-sandbox-policy)    │
        │                          │
        ▼                          │
  Push updated policy              │
        │                          │
        ▼                          │
  Verify reload succeeded ─────────┘

Step 1: Create sandbox with initial policy

openshell sandbox create --name dev --policy ./initial-policy.yaml -- claude

Sandboxes stay alive by default for iteration. Add --no-keep only when the sandbox should be deleted automatically after the initial session.

Step 2: Monitor logs for denied actions

In a separate terminal or as the agent:

openshell logs dev --tail --source sandbox

Look for log lines with action: deny -- these indicate blocked network requests. The logs include:

  • Destination host and port (what was blocked)
  • Binary path (which process attempted the connection)
  • Deny reason (why it was blocked)

Step 3: Pull the current policy

openshell policy get dev --full > current-policy.yaml

The --full flag includes the effective policy, including provider-composed entries. Use --base instead when the editable base policy is needed without provider-composed entries. Before resubmitting a --full result, review composed entries and prefer incremental updates or the base policy when appropriate.

Step 4: Modify the policy

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
9k
Forks
1k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
openshell-cli
Source
github.com/nvidia/openshell