> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Exception hierarchy for the Python SDK, how to catch specific errors, and retry patterns for transient failures.

```python theme={null}
from declaw import (
    SandboxException,
    TimeoutException,
    NotFoundException,
    AuthenticationException,
    InvalidArgumentException,
    NotEnoughSpaceException,
    TemplateException,
    BuildException,
    FileUploadException,
    GitAuthException,
    GitUpstreamException,
    CommandExitException,
    InsufficientBalanceException,
    RateLimitException,
)
```

All SDK exceptions inherit from `SandboxException`, which inherits from the built-in `Exception`. You can catch the base class to handle any Declaw error, or catch specific subclasses for granular handling.

## Exception hierarchy

```
Exception
└── SandboxException
    ├── TimeoutException
    ├── NotFoundException
    ├── AuthenticationException
    ├── InvalidArgumentException
    ├── NotEnoughSpaceException
    ├── FileUploadException
    ├── GitAuthException
    ├── GitUpstreamException
    ├── CommandExitException
    ├── InsufficientBalanceException
    ├── RateLimitException
    └── TemplateException
        └── BuildException
```

***

## `SandboxException`

Base class for all Declaw exceptions.

```python theme={null}
class SandboxException(Exception):
    def __init__(self, message: str = "", *, sandbox_id: str | None = None):
        self.sandbox_id = sandbox_id
        ...
```

**Attributes**

| Attribute    | Type          | Description                                           |
| ------------ | ------------- | ----------------------------------------------------- |
| `sandbox_id` | `str \| None` | The sandbox ID involved in the error, when available. |

***

## `TimeoutException`

Raised when an operation exceeds its configured timeout.

```python theme={null}
from declaw import Sandbox, TimeoutException

try:
    result = sbx.commands.run("sleep 300", timeout=10)
except TimeoutException as e:
    print(f"Command timed out in sandbox {e.sandbox_id}")
```

Inherits `sandbox_id` from `SandboxException`.

***

## `NotFoundException`

Raised when the sandbox or a requested resource does not exist (HTTP 404).

```python theme={null}
from declaw import Sandbox, NotFoundException

try:
    sbx = Sandbox.connect("nonexistent-id", api_key="key", domain="host:8080")
except NotFoundException as e:
    print("Sandbox not found:", e)
```

***

## `AuthenticationException`

Raised when the API key is missing or invalid (HTTP 401/403).

```python theme={null}
from declaw import Sandbox, AuthenticationException

try:
    sbx = Sandbox.create(api_key="wrong-key", domain="host:8080")
except AuthenticationException:
    print("Invalid API key")
```

***

## `InvalidArgumentException`

Raised when a method receives an argument that fails validation.

```python theme={null}
from declaw import NetworkPolicy, InvalidArgumentException

try:
    NetworkPolicy(allow_out=["not-a-valid-entry!!"])
except InvalidArgumentException as e:
    print("Bad argument:", e)
```

***

## `NotEnoughSpaceException`

Raised when the sandbox filesystem is full and a write operation fails.

```python theme={null}
from declaw import NotEnoughSpaceException

try:
    sbx.files.write("/data/large_file.bin", large_bytes)
except NotEnoughSpaceException:
    print("Sandbox disk is full")
```

***

## `CommandExitException`

Raised when a command exits with a non-zero exit code. Contains the full stdout, stderr, and exit code.

```python theme={null}
class CommandExitException(SandboxException):
    def __init__(
        self,
        message: str = "",
        *,
        exit_code: int = 1,
        stdout: str = "",
        stderr: str = "",
        sandbox_id: str | None = None,
    ):
        self.exit_code = exit_code
        self.stdout = stdout
        self.stderr = stderr
```

**Attributes**

| Attribute   | Type  | Description                |
| ----------- | ----- | -------------------------- |
| `exit_code` | `int` | The process exit code.     |
| `stdout`    | `str` | Accumulated stdout output. |
| `stderr`    | `str` | Accumulated stderr output. |

```python theme={null}
from declaw import CommandExitException

try:
    result = sbx.commands.run("python3 -c \"raise ValueError('oops')\"")
except CommandExitException as e:
    print(f"Exit code: {e.exit_code}")
    print(f"Stderr: {e.stderr}")
```

<Note>
  `CommandExitException` is only raised by `CommandHandle.wait()`. The main
  `sbx.commands.run()` method returns a `CommandResult` with a non-zero
  `exit_code` rather than raising — you must check `result.exit_code` yourself
  unless you use a background handle.
</Note>

***

## `TemplateException`

Base class for template-related errors.

***

## `BuildException`

Raised when a `Template.build()` call fails.

```python theme={null}
from declaw import BuildException

try:
    info = Template.build(template, "my-alias", api_key="key", domain="host:8080")
except BuildException as e:
    print("Build failed:", e)
```

***

## `FileUploadException`

Raised when uploading a file to the sandbox fails (e.g. network error during multipart upload).

***

## `GitAuthException`

Raised when git operations inside the sandbox fail due to authentication errors.

***

## `GitUpstreamException`

Raised when git operations fail due to upstream repository errors.

***

## `InsufficientBalanceException`

Raised when the account has insufficient balance to start or continue a
sandbox operation (HTTP 402).

```python theme={null}
from declaw import Sandbox, InsufficientBalanceException

try:
    sbx = Sandbox.create(api_key="key", domain="host:8080")
except InsufficientBalanceException as e:
    print(f"Top up required for wallet: {e.wallet_type}")
```

**Attributes**

| Attribute     | Type  | Description                                                        |
| ------------- | ----- | ------------------------------------------------------------------ |
| `wallet_type` | `str` | Which wallet is out of balance (e.g. `"sandbox"`, `"guardrails"`). |

***

## `RateLimitException`

Raised when the account exceeds its rate limit (HTTP 429). Inspect
`retry_after` and back off before retrying.

```python theme={null}
import time
from declaw import Sandbox, RateLimitException

try:
    sbx = Sandbox.create(api_key="key", domain="host:8080")
except RateLimitException as e:
    if e.retry_after:
        time.sleep(e.retry_after)
```

**Attributes**

| Attribute     | Type            | Description                                                   |
| ------------- | --------------- | ------------------------------------------------------------- |
| `retry_after` | `float \| None` | Seconds to wait before retrying, when provided by the server. |
| `limit`       | `int \| None`   | Configured request limit for the window.                      |
| `remaining`   | `int \| None`   | Requests remaining in the current window.                     |

***

## Catching all Declaw errors

```python theme={null}
from declaw import SandboxException

try:
    sbx = Sandbox.create(api_key="key", domain="host:8080")
    result = sbx.commands.run("my-command")
except SandboxException as e:
    print(f"Declaw error: {e}")
    if e.sandbox_id:
        print(f"Sandbox ID: {e.sandbox_id}")
finally:
    sbx.kill()
```

***

## Retry patterns

### Simple retry with backoff

```python theme={null}
import time
from declaw import Sandbox, TimeoutException, SandboxException

def run_with_retry(cmd: str, retries: int = 3, delay: float = 1.0) -> str:
    sbx = Sandbox.create(api_key="key", domain="host:8080")
    try:
        for attempt in range(retries):
            try:
                result = sbx.commands.run(cmd, timeout=30)
                return result.stdout
            except TimeoutException:
                if attempt < retries - 1:
                    time.sleep(delay * (2 ** attempt))
                else:
                    raise
    finally:
        sbx.kill()
```

### Retry with tenacity

```python theme={null}
from tenacity import retry, stop_after_attempt, wait_exponential
from declaw import Sandbox, TimeoutException

@retry(
    reraise=True,
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10),
    retry=lambda e: isinstance(e, TimeoutException),
)
def run_command(sbx: Sandbox, cmd: str) -> str:
    result = sbx.commands.run(cmd, timeout=30)
    return result.stdout
```

### Handle non-zero exit codes

```python theme={null}
from declaw import Sandbox

sbx = Sandbox.create(api_key="key", domain="host:8080")
try:
    result = sbx.commands.run("python3 risky_script.py")
    if result.exit_code != 0:
        print(f"Script failed (exit {result.exit_code})")
        print(f"Stderr: {result.stderr}")
    else:
        print(result.stdout)
finally:
    sbx.kill()
```

### Async error handling

```python theme={null}
import asyncio
from declaw import AsyncSandbox, TimeoutException

async def main():
    sbx = await AsyncSandbox.create(api_key="key", domain="host:8080")
    try:
        result = await sbx.run_command("long-running-task", timeout=60)
        print(result.stdout)
    except TimeoutException:
        print("Timed out — killing sandbox")
    finally:
        await sbx.kill()

asyncio.run(main())
```
