Cortex Sandboxes SDK reference¶
This page is the reference for the snowflake.sandbox Python API.
To install the SDK and configure a Snowflake connection, see the
Cortex Sandboxes quickstart.
Common operations¶
| To do this | Use |
|---|---|
| Create a sandbox and run one command | Sandbox.create(), then sb.exec() |
| Name the Snowflake connection | connection= on Sandbox.create(), or with using(...) |
| Stream output as it is produced | sb.exec_stream() |
| Start something and come back to it later | Sandbox.create(command=...), then sb.wait() |
| Reattach to a sandbox you already created | Sandbox.connect(name) |
| See what sandboxes you are holding | Sandbox.list() |
| Terminate a sandbox | sb.terminate(), or use Sandbox.create() as a with block |
| Read or write a file inside the sandbox | sb.read_text(), sb.write_text(), sb.upload_file() |
| Read a Snowflake stage from inside the sandbox | StageMount, passed as stage_mounts= |
| Give the sandbox a credential | Secret, passed as secrets= |
| Reach a host outside the default egress set | Egress(external_access_integrations=...) |
| Read the container’s output | sb.logs() |
| Run an interactive shell | sb.shell() |
| Keep a warm agent process alive | App, agent_session() |
Everything above is documented in full below. For the errors these calls raise, see the error reference.
Public surface¶
| Group | Name | What it is |
|---|---|---|
| Start here | Sandbox | A Cortex Sandbox (sync API). |
AsyncSandbox | A Cortex Sandbox (async API). | |
Image | A container image built from a catalog base. | |
| Connections | using | Run a block against one Snowflake connection, without touching global state. |
Config | Resolved SDK configuration. | |
ConnectionLike | Represent a PEP 604 union type | |
current_config | Resolve the AMBIENT SDK configuration – the one a caller who named no connection gets (account, host, token). | |
resolve_config | Resolve a Config from a connection + overrides, WITHOUT storing it anywhere. | |
get_snowflake_connection | Return a Snowflake connection for code running inside a Cortex Sandbox. | |
close_connections | Close the connector sessions, clear the resolution cache, drop bound transports. | |
shutdown | Close the process-wide async transport’s HTTP client(s) and drop it. | |
| Results of running something | ExecResult | Result of a non-streaming Sandbox.exec(). |
RunResult | The outcome of a detached run (spawn + wait). | |
StreamLine | One logical line yielded by Sandbox.exec_stream(). | |
ExecStream | Async iterator over the lines of a streaming exec. | |
SyncExecStream | Synchronous iterator over the lines of a streaming exec. | |
SandboxFailure | The server’s explanation for a failed sandbox. | |
| Configure the container | Egress | Controls outbound network access from the sandbox. |
Secret | A Snowflake SECRET brokered to the sandbox, referenced by name. | |
StageMount | A Snowflake stage or workspace FUSE-mounted into the container. | |
McpServer | An EXTERNAL MCP SERVER to expose to the sandbox, referenced by FQN. | |
Bundle | Source-tree slice bundled and uploaded to the sandbox. | |
broker_env_var | The environment variable a secret’s placeholder lands in without env_var. | |
| Find and manage sandboxes | list_sandboxes | List sandboxes the caller’s PAT has access to. |
list_sandboxes_async | List sandboxes the caller’s PAT has access to. | |
get_sandbox | Rehydrate a Sandbox by id (e.g. after a driver restart). | |
get_sandbox_async | Rehydrate an AsyncSandbox by id (e.g. after a driver restart). | |
get_sandbox_by_name | Look up a sandbox by its caller-assigned name= (Modal Sandbox.from_name). | |
get_sandbox_by_name_async | Look up a sandbox by its caller-assigned name= (Modal Sandbox.from_name). | |
destroy_sandbox | Destroy a sandbox by id. Idempotent for an already-absent container. | |
destroy_sandbox_async | Destroy a sandbox by id. Idempotent for an already-absent container. | |
| Detached runs and shells | Job | A handle to a detached run (async API). |
SyncJob | A handle to a detached run (sync API) — the blocking counterpart of Job. | |
Shell | One interactive shell session on a sandbox. | |
SyncShell | One interactive shell session on a sandbox (blocking API). | |
ShellClosed | Raised when input is sent to a shell whose process has exited. | |
| Long-lived agent sessions | App | A named collection of deployable functions. |
Function | A deployable function with .remote() and .local() calling conventions. | |
Session | A handle to a warm session. | |
SessionApp | Returned by @app.session — a deployable stateful Session class. | |
agent_session | Create-or-resume a keep-alive Session keyed by key. | |
agent_session_sync | Create-or-resume a keep-alive Session keyed by key — synchronous counterpart of agent_session. | |
session_loop | Run the daemon-side message loop (the in-container half of a warm agent). | |
| Deploy local code | DeployPlan | What a deploy would do (used by –dry-run and surfaced on deploy). |
DeployResult | The outcome of a deploy: what was planned, what happened, and what went wrong. | |
deploy_async | Spawn a detached Job from a DeploySpec and return a handle. | |
deploy_async_sync | Spawn a detached job synchronously — the blocking counterpart of deploy_async. | |
| Files | FileInfo | Metadata for one path, returned by Sandbox.list_files() / .stat(). |
FileWatchEvent | One filesystem-change event yielded by Sandbox.watch(). | |
FileWatchEventType | Coarse change class for a Sandbox.watch() event. | |
| MCP servers | list_mcp_servers | List the account’s EXTERNAL MCP SERVERs with this user’s auth status. |
mcp_server_status | Read the live connection status the platform publishes inside the sandbox. | |
validate_mcp_servers | Validate mcp_servers= and convert it to wire entries. | |
mcp_token_env_var | The environment variable an MCP server’s dummy token lands in. | |
McpServerInfo | One EXTERNAL MCP SERVER in the account, with your authorization status. | |
McpConnectionStatus | The live, in-sandbox connection status of one EXTERNAL MCP SERVER. |
Copy-paste form:
RunResult is the outcome type of a detached run (create(command=...) + wait).
The Sandbox object¶
Sandbox represents one container. Construction is lazy—no network call happens
until you call create(), or enter the object as a with context manager.
Create¶
Sandbox.create is the one-call factory. It creates the sandbox and returns a live handle.
Key parameters of Sandbox.create() (all keyword-only):
| Parameter | Type | Default | Description |
|---|---|---|---|
image | str | Image | "" | Image name. Empty string uses the deployment’s default image, which is the recommended form (the fleet default moves). |
code | str | Path | None | None | Bundle + upload a local project directory . |
command | Sequence[str] | None | None | Managed main process (for wait / a warm daemon / logs). |
memory | MemoryTier | "4g" | Memory tier: "1g" | "4g" | "8g" | "16g" | "32g" | "64g". |
cpu | float | None | None | CPU in cores, overriding the tier default (1g:1, 4g:2, 8g:3, 16g:4, 32g:6, 64g:8). Fractional allowed (0.5 = half a core). Sets only CPU; memory stays tier-selected. |
env | Mapping[str, str] | None | None | Environment variables for in-container processes. |
secrets | Sequence[Secret | Mapping[str, str]] | None | None | Snowflake SECRETs to broker in — the sandbox gets a dummy . |
egress | Egress | Mapping[str, object] | None | None | Outbound scope. Omitted applies the baseline (Snowflake, its stages, and the package-manager hosts — GitHub is not among them); e.g. Egress(external_access_integrations=["MY_EAI"]) to grant specific hosts, or Egress.only() to close egress. A caller-supplied host list is retired and refused . |
code_stage | str | None | None | Snowflake stage the bundle is uploaded to / pulled from (needed when code= is set and command runs a bundled file). |
name | str | None | None | Optional stable name for the container. |
stage_mounts | Sequence[StageMount] | None | None | Mount Snowflake stages into the container filesystem — a sequence of StageMount (e.g. StageMount.from_stage("@DB.SCHEMA.DATA", mount_path="/mnt/data")). The supported path for bulk/stage-backed data. |
mcp_servers | Sequence[str | McpServer | Mapping[str, object]] | None | None | EXTERNAL MCP SERVERs to expose to the container — FQN strings (["<your_database>.<your_schema>.GITHUB_MCP_SERVER"]) or McpServer.external(fqn, name=...). Snowflake resolves the endpoint, API integration, and your OAuth secret server-side; the container sees a dummy in SANDBOX_MCP_TOKEN_<NAME> and the egress proxy swaps in the real token. Servers you have not authorized are omitted — list_mcp_servers() shows which those are. |
tags | Mapping[str, str] | None | None | User-defined labels for the sandbox — a dict[str, str] (e.g. {"team": "ml", "env": "dev"}). Retrieve later with sb.get_tags(). |
role | str | None | None | Snowflake role to assign to this sandbox’s session. Naming a connection is sufficient — role-scoped sessions are minted on demand. When omitted, the sandbox runs with the default session’s primary role and all of the user’s secondary roles. Pass role to limit the sandbox to that role’s grants. The role is returned by the server and stored on sb.role. |
mount_snowflake_config | bool | True | Have the platform render the sandbox’s Snowflake config so snow / cortex / the connector authenticate inside the container with no setup. Set False to manage your own: the platform then writes no connections.toml, sets no SNOWFLAKE_HOME, and omits the SNOWFLAKE_* connection env — so an opting-out caller must supply a full connection itself (a bare snow sql / connector.connect() otherwise fails with “Connection default is not configured”). |
omit_snowflake_token | bool | False | If True, code in the sandbox can’t connect to Snowflake as you. Use it for workloads that shouldn’t have Snowflake access. |
idle_suspend | dt.timedelta | str | None | None | Idle-suspend timeout — how long the sandbox may sit idle (no inbound traffic) before the platform auto-suspends it, overriding the platform default of 10 minutes. A datetime.timedelta or a duration string ("30m", "2h", "1h30m"); a bare number is rejected because its unit would be ambiguous. Resolution is minutes, so one minute is the smallest value you can set: a value that is not a whole number of minutes (e.g. "90s") raises rather than being silently rounded. Zero and negatives are rejected too: 0 is the create field’s “no opinion” sentinel, for which the server substitutes its own default exactly as it does for an absent field, so it is not a threshold the platform can apply — omit idle_suspend (or pass None) to take the 10 minute default. There is no caller-facing way to say “never idle-suspend”: the server caps this value at 24 hours and there is no separate maximum sandbox lifetime to set it above. Idleness is measured from inbound traffic only, so CPU and disk work do not hold a sandbox open — a busy compute loop with nothing arriving is suspended on schedule — while merely polling a sandbox’s status is inbound traffic and keeps it alive and consuming compute. A suspend preserves only the filesystem. Memory is not preserved: the container is frozen, its filesystem snapshotted, and then stopped and deleted, and the resume builds a fresh container from the base image plus that snapshot. The platform also sends SIGUSR1 (default action: terminate) shortly before the checkpoint, which is why the SDK’s daemon and detached wrappers ignore it. So tmpfs (/tmp, /dev, /run, /snowflake/stages) and every running process are lost and only persistent rootfs writes (/var/tmp, /var/log, /home, /root) come back. Nothing re-executes the container’s command on resume, so a detached run or a warm daemon does not restart by itself. |
connection | ConnectionLike | None | None | Pick the Snowflake connection for this sandbox — a name in ~/.snowflake/connections.toml, a live snowflake.connector connection you already opened, a Snowpark Session, or a Config assembled from raw credentials (mirroring the connector’s connect(connection_name=...)). The handle carries it onward. The transport is pooled per credential identity, so exiting terminates the sandbox and leaves the HTTP client for the next call on that connection (shutdown() releases it, or lifecycle.shutdown_sync() from a sync-only program); two sandboxes on one connection share a client. Resolution is memoised, so naming it call after call costs one login. role= composes with a connection name only — a live connection or Snowpark Session is already logged in and raw credentials carry their own role, so any of those is refused rather than silently ignored. Mutually exclusive with transport=. Omit to use an enclosing using() block, else the default connection. |
wait | bool | True | When True (the default), create() blocks until the container is live. When False, returns immediately with the container in pending state; call sb.wait_until_ready() to block later (useful for parallel creates). |
Use create() in a with block to destroy the container on exit:
The earlier sandbox.toml manifest format is retired. Use Sandbox.create() (with code=) or the App interface instead.
Properties¶
| Property | Description |
|---|---|
sb.cpu | The effective CPU allocation in cores. |
sb.created_at | The server-reported container creation timestamp, if available. |
sb.exit_code | The managed process’s exit code, or None if it has not exited. |
sb.failure | Why this sandbox failed, or None if it has not. |
sb.generation | A best-effort restart/resume counter, or None if the server reports none. |
sb.id | The server-assigned container ID. |
sb.image | The image name the sandbox was constructed with. |
sb.is_running | True if the sandbox has not yet terminated (cached status is not terminal). |
sb.is_terminated | True if the sandbox has terminated (cached status is terminal). |
sb.memory | The effective memory tier ('1g', '4g', '8g', '16g', '32g', or '64g'). |
sb.name | The sandbox’s name, once the server echoes it. |
sb.role | The Snowflake role this sandbox was created under, or None if not set. |
sb.server_status | The server’s raw status string, or "" if none has been reported. |
sb.status | The last status the server reported for this container. |
sb.stderr | The managed command process’s stderr. See stdout. |
sb.stdout | The managed command process’s stdout, as an async-iterable of lines. |
sb.stopped_at | The server-reported stop timestamp, if available. |
sb.tags | The tags (server labels) attached to this sandbox, as last known here. |
Sandbox methods¶
Create and connect¶
| Method | Description |
|---|---|
Sandbox.connect() | Reconnect to an existing sandbox by its NAME (the identity list shows), for example after your driver restarts. The name is unique per owner. |
Sandbox.list() | Return the sandboxes the caller can see, optionally filtered by status. |
Sandbox.connect()¶
Note
A name reaches only the sandboxes the default list view returns, so it
cannot reattach to one the controller reports failed/unknown, nor one
stopped more than 24h ago – those raise SandboxNotFoundError here. Keep
the id as well if you need to reach a sandbox to diagnose why it died;
the by-id read has no such filter.
Sandbox.list()¶
Run commands¶
| Method | Description |
|---|---|
sb.exec() | Run a command to completion and return an ExecResult. |
sb.exec_stream() | Stream a command’s output line by line. Returns a SyncExecStream iterator. |
sb.exec()¶
Note
There is no pty= option: exec is pipe-based with separate
stdout/stderr. For pseudo-terminal semantics use shell().
stdin, env, working_dir, and text=False are present in the
signature but not supported by this method — passing any of them
raises SandboxError. Use exec_stream(cmd, env=..., working_dir=...)
for per-call environment or working directory, or set env once at
create time with Sandbox.create(env=...).
sb.exec_stream()¶
Note
There is no pty= option: exec_stream uses pipes with separate
stdout/stderr. For TTY semantics use shell().
env REPLACES the environment for this call – it does not merge into
the sandbox’s own. Passing env={"FOO": "1"} runs the command with
FOO and nothing else: HOME, anything set at create time via
Sandbox.create(env=...), and the proxy and CA variables the sandbox
needs to reach the network (HTTP_PROXY, HTTPS_PROXY,
NO_PROXY, CNG_CA_CERT, REQUESTS_CA_BUNDLE,
NODE_EXTRA_CA_CERTS) are all gone. Measured on a live sandbox: about 30
variables without env=, 3 with it – and two of those three,
PWD and SHLVL, are set by the shell itself.
The consequence is easy to misread, because nothing points back at
env=: a command that needs the network – pip install, curl,
anything using requests – starts failing with a proxy or TLS error
purely because a variable was passed. Lost PATH is less visible,
since a shell substitutes a default, so bare-name commands still run.
To add a variable while keeping the rest, set it inside the command
instead of passing env= – for example
sb.exec_stream(["/bin/sh", "-c", "FOO=1 my-command"]), which keeps the
rest of the environment – or set it once for the whole sandbox with Sandbox.create(env=...),
which IS visible to later calls.
Wait and send¶
| Method | Description |
|---|---|
sb.wait_until_ready() | Block until the container is live (for sandboxes created with wait=False). |
sb.wait() | Wait for a detached run to finish and return its result. |
sb.send() | Send a message to a warm daemon in the sandbox and return its reply. |
sb.wait_until_ready()¶
sb.wait()¶
sb.send()¶
Manage the container¶
| Method | Description |
|---|---|
sb.refresh() | Re-read this container’s state from the server and return its status. |
sb.refresh_live_status() | Read the LIVE status of the managed process, updating status / server_status. |
sb.logs() | Return the container’s captured stdout and stderr as a string. |
sb.shell() | Open an interactive shell session inside the sandbox. |
sb.suspend() | Suspend the sandbox: snapshot its filesystem, then tear the container down. |
sb.resume() | Wake a suspended sandbox without running anything in it. |
sb.destroy() | Deprecated: use terminate() instead. |
sb.terminate() | Terminate the sandbox (idempotent). |
sb.poll() | Return the exit code if the run has finished, or None if still running. |
sb.get_tags() | Return the key-value tags attached to this sandbox. |
sb.set_tags() | Set key-value tags for this sandbox (must be called before creation). |
sb.refresh()¶
sb.refresh_live_status()¶
sb.logs()¶
sb.shell()¶
Note
shell is a terminal verb, not the SSH protocol. There is no port 22,
no key exchange, and no scp. The shell is a PTY driven over the
Snowflake REST path.
sb.suspend()¶
sb.resume()¶
sb.poll()¶
Note
Completion is read from the run’s own result report, not from the
container’s status: a sandbox created with command= routinely still
reports ready after its command has exited. So a code comes back as soon
as the run finishes, without waiting for the container to go away.
sb.get_tags()¶
sb.set_tags()¶
Note
Tags can only be set before create() is called – pass tags= to
Sandbox.create(). There is no relabel endpoint, so calling this on an
already-created sandbox raises SandboxError.
File operations¶
| Method | Description |
|---|---|
sb.upload_file() | Upload a local file into the running sandbox. |
sb.upload_dir() | Upload a local directory into the running sandbox and report what it sent. |
sb.download_file() | Download a file from the running sandbox to a local path. |
sb.read_text() | Read a text file from the sandbox and return its contents. |
sb.read_bytes() | Read a binary file from the sandbox and return its contents. |
sb.write_text() | Write a text string to a file inside the sandbox. |
sb.write_bytes() | Write bytes to a file inside the sandbox. |
sb.list_files() | List files and directories at path inside the sandbox. |
sb.make_directory() | Create a directory inside the sandbox. |
sb.stat() | Return metadata for a file or directory inside the sandbox. |
sb.remove() | Remove a file or directory inside the sandbox. |
sb.stage_put() | Not implemented – the stage/put route returns 501 on every backend. |
sb.stage_get() | Not implemented – the stage/get route returns 501 on every backend. |
sb.watch() | Stream filesystem-change events under path as an iterator. |
sb.upload_file()¶
sb.upload_dir()¶
Note
There is no bulk route, so this is one request per file and the
per-file MAX_FILE_BYTES ceiling still applies – an oversize file is
refused before the first byte is sent, so a tree never half-uploads.
dry_run=True sends nothing and so raises nothing: it returns the plan
with any offenders listed in plan.oversized. Mount a stage
(StageMount) for bulk data. Symlinks are never followed, in either
direction, and appear in plan.skipped as symlink-dir /
symlink-file rather than vanishing silently. An empty directory is not
uploaded – there is no file to create it under – and is reported as
empty-dir.
sb.download_file()¶
Note
Written to a temp file beside the destination and renamed on success, so a download that fails part-way leaves neither a truncated file where a whole one is expected nor a directory tree the caller did not have before.
sb.read_text()¶
sb.read_bytes()¶
sb.write_text()¶
sb.write_bytes()¶
sb.list_files()¶
sb.make_directory()¶
sb.stat()¶
sb.remove()¶
sb.stage_put()¶
Note
Not implemented on any backend: the stage/put route answers 501, so this
always raises SandboxNotImplementedError. Mount the stage with StageMount
and write through the mount instead.
sb.stage_get()¶
Note
Not implemented on any backend: the stage/get route answers 501, so this
always raises SandboxNotImplementedError. Mount the stage with StageMount
and read through the mount instead.
sb.watch()¶
Result and stream types¶
Sandbox.exec() returns an ExecResult, a named tuple with the following fields:
| Field | Description |
|---|---|
stdout | Decoded standard output. |
stderr | Decoded standard error. |
exit_code | The in-container process exit code. |
elapsed_ms | Wall-clock time on the server side, not including network round-trip. |
Sandbox.exec_stream() yields StreamLine named tuples:
| Field | Description |
|---|---|
stream | The channel the line came from: "stdout" or "stderr". |
data | The line text, with its trailing newline stripped. |
The SyncExecStream returned by exec_stream() exposes exit_code, elapsed_ms, and error after the iterator drains. It does not raise on a non-zero exit; check stream.exit_code. It raises SandboxExecError on an error frame (meaning the exec never produced a result).
Sandbox.wait() returns a RunResult:
| Field | Description |
|---|---|
status | One of "succeeded", "failed", or "timed_out". |
exit_code | The entry command’s exit code, or None if unknown. |
result | The parsed result artifact, or None if no result was produced. |
logs_ref | The container ID that the logs are read from. |
File and watch types¶
FileInfo – Metadata for one path, returned by Sandbox.list_files() / .stat(). Fields: path, is_dir, size_bytes, modified_at.
Mirrors Modal’s Sandbox.filesystem.stat shape. Both producers are implemented (files.py runs an in-container python3 helper over exec); modified_at is a Unix epoch (seconds, UTC) or None when the server did not report it.
FileWatchEvent – One filesystem-change event yielded by Sandbox.watch(). Fields: paths, type.
Mirrors Modal’s FileWatchEvent: paths are the absolute in-sandbox path(s) the change concerns (usually one), and type is the coarse FileWatchEventType.
FileWatchEventType – Coarse change class for a Sandbox.watch() event. Members: Unknown, Access, Create, Modify, Delete.
Mirrors Modal’s FileWatchEventType. The sandbox observes raw Linux inotify(7) masks and maps them onto this small enum — several inotify bits collapse to one class (e.g. IN_MODIFY and IN_CLOSE_WRITE both read as Modify; IN_CREATE and IN_MOVED_TO both as Create). A mask that maps to nothing meaningful surfaces as Unknown rather than being dropped, so a contract drift is visible instead of silent. str-valued so it compares equal to its wire string and JSON-serializes cleanly.
Images, secrets, egress, and bundles¶
The declarative specs (Image, Secret, Egress, Bundle, StageMount, DeploySpec) describe a deployable app. @app.function(...) takes them directly, and they compile to a DeploySpec. There is no manifest file.
Image¶
An Image is a reference to a catalog base image. Use Image.from_catalog() to select one:
Pass an Image (or a plain catalog-name string) directly to Sandbox.create():
SDK-side image dependency baking (the layered-image builders
pip_install,run_commands,copy_local_*,env,workdir, andbuild) is temporarily unavailable and will return in a later release. Until then, either use a base image that already includes the dependencies you need, or install them into the running sandbox withsb.exec(["pip", "install", ...]). Public PyPI is in the always-on egress baseline, so a runtime install needs noegressconfiguration and no external access integration.
Secret¶
Secret is how a sandbox uses an external credential. It references a Snowflake SECRET object. The secret’s value is not placed in the container; the platform supplies it on outbound requests to the declared host. See Cortex Sandboxes concepts and security.
Secret.from_name(fqn, *, env_var, host, hosts)references a SECRET by fully qualified name.hostandhostsare mutually exclusive.- The env var that receives the value is
env_varif set, otherwise derived from the fully qualified name viabroker_env_var.
Egress¶
Egress controls outbound network access. There are exactly three levels, and the middle one is the default:
| Level | How | Reaches |
|---|---|---|
| baseline | the default, or allow_public_package_registries=True | Snowflake, the cloud-storage stages, and roughly 43 package-manager hosts |
| closed | allow_public_package_registries=False or Egress.only() | Snowflake and its stages only |
| granted | external_access_integrations=(...) | the above, plus whatever the integration’s network rules resolve to |
The baseline is not the internet. It is a curated host set that the platform maintains, and anything outside it is refused. A host you need that is not on that list requires an external access integration at every level, including the baseline.
Key fields:
| Field | Description |
|---|---|
allow_public_package_registries | Whether the sandbox reaches the public package registries described above. None uses the platform default. Setting it to False does not stop egress: Snowflake and its stages remain reachable, and an external access integration still grants. |
allow_github | Deprecated and inert. The platform is removing the GitHub/dbt flag, so those hosts need an external access integration (or a secret naming one of them). |
allow_pypi | Deprecated and inert. Public PyPI is in the always-on package-manager group; no flag was ever needed. |
external_access_integrations | Named external access integrations — the only per-host grant. Their network rules carry ports. Required for OAUTH2 secrets. |
broker_env_var(fqn, *, key=None) returns the environment variable name that the secret broker uses for a fully qualified SECRET name. Pass key="password" or "username" for a PASSWORD-type secret.
Bundle¶
A Bundle is a source-tree slice uploaded to the sandbox.
Bundle.from_dir(root, *, include, exclude) constructs a bundle from a root directory, where include and exclude are glob patterns.
StageMount¶
A StageMount mounts a Snowflake stage or workspace into the container.
StageMount.from_stage(name, *, mount_path, readonly)mounts a regular Snowflake stage.StageMount.from_workspace(name, *, mount_path, readonly)mounts a Snowflake workspace.
McpServer¶
An McpServer declares an EXTERNAL MCP SERVER the sandbox may call. Addressing is by fully-qualified name, the same form Cortex Agents take; Snowflake resolves the endpoint, the API integration, and the calling user’s OAuth secret server-side. A bare FQN string works wherever an McpServer does.
McpServer.external(fqn, *, name, profile)declares a server.namekeysmcp.jsonand is uppercased into the dummy-token env var (SANDBOX_MCP_TOKEN_GITHUB); it defaults to the FQN’s last part.list_mcp_servers()lists the account’s external MCP servers with your per-user authorization status (CONNECTED,NOT_CONNECTED,NEEDS_REAUTH,INTEGRATION_NOT_FOUND,UNKNOWN). OnlyCONNECTEDservers reach a sandbox.- The OAuth token is not placed in the container. The platform supplies it on requests to the server’s host, the same way it handles a
Secret.
DeploySpec¶
DeploySpec is the internal representation that the decorator API and the CLI compile to. It records the image, entry command, memory, CPU, environment, egress, secrets, bundle, and stage mounts for a deployment. You typically do not construct one directly.
The App interface¶
The App decorator layer lets you call a typed Python function in a sandbox with .remote(), or run it in-process with .local().
A registered Function supports:
| Method | Purpose |
|---|---|
fn.remote(**kwargs) | Deploy and run in a sandbox; returns a DeployResult. |
fn.spawn(**kwargs) | Start the run detached and return a live Sandbox handle; pull the result later with sb.wait(). |
fn.local(*args) | Run the function in-process, with no sandbox or deployment. |
fn.deploy_spec(**env_overrides) | Return the DeploySpec this function would deploy, for review or CI. |
A function can run in one of two modes:
- Process mode:
entry=is specified.fn.remote(**kwargs)passes keyword arguments as environment variable overrides, and the function body runs only when you callfn()orfn.local()directly. - Function mode: no
entry=. The function body runs in the container;fn.remote(**kwargs)JSON-encodes the keyword arguments and runs the body inside the sandbox.
You can also register a stateful, keep-alive class with @app.session(...). The class defines on_message(self, msg) for each turn and may use the @enter decorator to mark a cold-start hook. The resulting SessionApp.session(key=...) deploys the daemon and returns a Session you drive with s.send(msg).
session_loop(handle) is the in-container half of a warm agent. handle(msg, state) is called for each incoming message; state is a plain dict that persists across calls in process memory — for as long as the process lives. It runs forever. Note that an idle auto-suspend deletes the container rather than checkpointing its memory, so state does not survive one and the loop is not restarted; keep anything you cannot recompute on the persistent rootfs (under /var/tmp), not in state and not in /tmp.
Configuration and connections¶
Name the connection where you construct the thing. connection= is on every
entry point that does not already have a handle to carry it — Sandbox.create(),
Sandbox.connect(), Sandbox.list(), list_sandboxes(),
get_sandbox_by_name(), destroy_sandbox(), Job.from_id(), deploy_async(),
snowflake.sandbox.deploy.deploy_spec(), Session.from_id(), agent_session() —
and on both the sync and async twins:
It takes one of four things (typed as ConnectionLike), mirroring the connector’s
connect(connection_name=...): a connection name from
~/.snowflake/connections.toml, a live snowflake.connector connection you
already opened, a Snowpark Session — what you hold inside
Streamlit-in-Snowflake and stored procedures — or a Config of raw
credentials for a process that has no connections file at all:
A connection or Session you opened stays yours to close — the SDK reuses it and never closes what it did not open.
Resolution is memoised per connection and role, so passing it on every call costs one login, not one per call. Every handle returned carries the connection onward, so you name it once per handle rather than once per operation.
role= overrides the role the session uses when creating sandboxes, and is applied
when the session is minted — so it composes with a connection name only. A live
connection or Snowpark Session is already logged in, and raw credentials carry their
own role, so passing role= with any of those is refused rather than silently
ignored.
A role that cannot be applied is an error on every path — create(), and the read
paths (list_sandboxes(), get_sandbox_by_name(), Sandbox.connect(),
destroy_sandbox()) which used to tolerate it because role= there also carried the
server-side ?role= filter, valid under any credential. role= means one thing now:
the credential. The filter is still applied, as an internal narrowing consistent with
that credential rather than a second meaning you can reach independently. If omitted, the role comes from the connection. A role the user does
not hold is refused at login.
role is not an isolation boundary — user + account is. Listing with no role=
returns every sandbox you own across all your roles, so a sandbox created under one
role is still yours to see, attach to and terminate from a session at another.
Passing role= narrows the server-side filter; it does not partition ownership.
using() is the scoped form, for when a sequence of calls shares one connection
and repeating the argument would be noise. It binds a connection to a block:
It takes the same arguments as connection= plus the client knobs, and yields the
resolved config. Everything inside the block uses that connection. Nothing outside
it is affected: the binding lives in a contextvars.ContextVar, so it cannot
outlive the block, and two threads or asyncio tasks can hold two different
connections at the same time. A sandbox handle built outside a block keeps its
own connection when used inside one — using() decides where a newly acquired
transport comes from, and never re-points an existing one. A per-call role=
composes, minted from the block’s connection; repeating the same role is a no-op,
while a different one raises SandboxError rather than silently preferring either.
Name nothing and the SDK resolves an ambient connection, the same way the
snow CLI does: a credential in the environment (SNOWFLAKE_PAT /
SNOWFLAKE_TOKEN, which is how a process running inside a sandbox authenticates
with no config at all), else SNOWFLAKE_DEFAULT_CONNECTION_NAME or
default_connection_name in ~/.snowflake/config.toml.
Those two are the process-wide default, and the SDK deliberately offers no setter of its own for it:
One source of truth, honoured by every Snowflake library in the process — the
connector, the snow CLI, Snowpark, Streamlit — so your sandboxes and your cursors
cannot drift onto different accounts. A sandbox-only global would carry the same
value with a different blast radius, and a caller using the SDK alongside
snowflake.connector could end up running SQL against prod and sandboxes against
dev with nothing warning them.
It is read when a connection is resolved, not at import, so setting it works
mid-process — including from a notebook’s first cell, which a using() block cannot
span.
current_config() returns the resolved config — useful for checking which account,
host, and role the SDK picked. It is a reader: it never changes what a later call
resolves, and it does not report an enclosing using() block (use the value the
block yields for that).
close_connections() releases everything the process resolved — the connector sessions,
the resolution memo, and every connection-bound transport. It is a teardown, not an
un-configure: the next call resolves again and works.
get_snowflake_connection(**kwargs) returns a Snowflake connection for code running inside a Cortex Sandbox. Prefer it for in-sandbox code, production included. It resolves the credential the way the platform provides it, in order: SNOWFLAKE_PAT or SNOWFLAKE_TOKEN in the environment; a token file named by SNOWFLAKE_TOKEN_FILE_PATH, else /snowflake/session/token, together with SNOWFLAKE_ACCOUNT; then a [default] entry in the platform-rendered $SNOWFLAKE_HOME/connections.toml. Deployments differ in which of those they set, so code that reads only one of them fails where a credential is in fact present.
It also supplies three things hand-rolled connector code usually gets wrong: SNOWFLAKE_ACCOUNT_URL as a bare host, since the connector wants no scheme; role from SNOWFLAKE_ROLE and warehouse from SNOWFLAKE_WAREHOUSE, which are otherwise not applied, leaving the connector on the user’s default role; and a default login_timeout, so a closed-egress sandbox fails fast instead of hanging. You don’t need to set up TLS trust: the sandbox already trusts the platform’s egress proxy.
The helper is for in-sandbox use only, because the credential it reads exists only inside a sandbox. It refuses keyword overrides that would redirect the credential (account, host, token), raising ValueError; non-credential options such as warehouse, database, schema, and role pass through to connect().
When you need to build the connection yourself, see the in-sandbox example in Connect to Snowflake from the SDK, which covers the host handling the helper does for you.
Exceptions¶
Most errors raised by the SDK derive from SandboxError. Two categories fall outside that hierarchy: configuration problems raise RuntimeError (unconfigured client, unresolvable connection), and some transport/timeout conditions surface as RuntimeError (event loop closed) or asyncio.TimeoutError. Guard those separately.
| Exception | Meaning |
|---|---|
SandboxAuthError | HTTP 401/403 from the Cortex Sandboxes service. |
SandboxBuildError | Image build failed. |
SandboxConflictError | HTTP 409 — the resource already exists or is in a conflicting state. |
SandboxCreateError | A create reached a terminal failed state on the server. NOT retryable. |
SandboxError | Base class for every error raised by snowflake.sandbox. |
SandboxExecError | Non-zero exit from Sandbox.exec. Carries stdout/stderr/exit_code. |
SandboxExecTimeoutError | The server enforced its exec deadline, killing the command. |
SandboxFileTooLargeError | A file transfer exceeded the size the platform will carry. |
SandboxNotFoundError | HTTP 404 — container or resource missing. |
SandboxNotImplementedError | A surfaced SDK method has no backend and will not until one lands. |
SandboxNotReadyError | Container not ready: HTTP 409 cold-start exhausted, or async create timed out. |
SandboxRateLimitError | HTTP 429 after the SDK retry budget. |
SandboxTransportError | 5xx, DNS, connection, or read-timeout failures after retries. |
SandboxValidationError | HTTP 400 — the request parameters are invalid. |
ShellClosed | Raised when input is sent to a shell whose process has exited. |
| SandboxContractWarning | The server sent something the SDK does not recognize. Emitted as a warning rather than silently guessing. |
For error handling guidance including what to do for each error, see Cortex Sandboxes error reference.