# Files

Read and manage persistent files without starting or resuming sandbox compute. The existing Files API and SDK methods support reading, writing, listing, stat, directory creation, moving, and deletion while a sandbox is paused.

Files belong to the sandbox identity and survive successful checkpointed pause/resume. Deleting the sandbox removes its current filesystem; create a [snapshot or fork](/docs/sandbox/snapshot-restore) first when you need a reusable copy.

## Access While Paused

No new endpoint, flag, or SDK is required. Offline access uses platform storage workers and storage services while the sandbox's guest compute remains stopped; it does not execute guest commands. File operations do not trigger `auto_resume`, including when it is enabled, and work when `auto_resume` is disabled.

| Sandbox state | File operations |
|---|---|
| Running | Read and modify the current guest filesystem. |
| Filesystem-only pause | Read, write, stat, list, mkdir, move, and delete persistent files without resuming compute. Successful changes are durable and visible on the next resume. |
| Memory pause | Read, stat, and list the committed RootFS without resuming. Writes, mkdir, move, and delete return `409 conflict` to preserve the retained process state. |
| Lifecycle transition | Requests can wait for the current operation or return a conflict/unavailable response. They never start compute to work around a conflict. |

To edit files while preserving a memory checkpoint's processes, explicitly resume with memory first. File access never silently drops a memory checkpoint or switches to a fresh process launch.

Offline access covers the persistent RootFS. Virtual filesystems and runtime mounts, such as `/tmp`, `/proc`, `/sys`, `/dev`, managed credentials, and template tmpfs mounts, require the runtime and are unavailable offline. Offline operations reject these paths, including aliases through symlinks; directories containing runtime mounts cannot be moved or deleted offline. SSH/SFTP and [file watching](#watch-files) also require running compute; watch keeps its existing `auto_resume` behavior.

Use a handle for an existing sandbox; creating the handle does not claim or resume compute:

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `sandbox := client.Sandbox("sb_abc123")
entries, err := sandbox.ListFiles(ctx, "/workspace")
if err != nil {
    log.Fatal(err)
}
content, err := sandbox.ReadFile(ctx, "/workspace/output.txt")
if err != nil {
    log.Fatal(err)
}
// During a filesystem-only pause, this persists without resuming compute.
_, err = sandbox.WriteFile(ctx, "/workspace/input.txt", []byte("next task"))
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Listed %d entries; read %d bytes\\n", len(entries), len(content))`
    },
    {
      label: "Python",
      language: "python",
      code: `sandbox = client.sandbox("sb_abc123")
entries = sandbox.list_files("/workspace")
content = sandbox.read_file("/workspace/output.txt")
# During a filesystem-only pause, this persists without resuming compute.
sandbox.write_file("/workspace/input.txt", b"next task")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const sandbox = client.sandbox('sb_abc123');
const entries = await sandbox.listFiles('/workspace');
const content = await sandbox.readFile('/workspace/output.txt');
// During a filesystem-only pause, this persists without resuming compute.
await sandbox.writeFile('/workspace/input.txt', 'next task');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files ls /workspace -s sb_abc123
s0 sandbox files cat /workspace/output.txt -s sb_abc123
# During a filesystem-only pause:
s0 sandbox files write /workspace/input.txt --data "next task" -s sb_abc123`
    }
  ]}
/>

Offline writes publish a new immutable filesystem generation before returning success. They serialize with resume, restore, snapshots, deletion, and other writes. A failed operation does not publish its partial changes. If a connection is lost, the mutation outcome may be uncertain: inspect the file or directory before retrying a move, delete, or write. A write replaces one file atomically; several API calls are separate operations.

## File Model

### FileInfo

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | File name |
| `path` | string | Requested path, or a directory entry path beneath it; use absolute paths for unambiguous results |
| `type` | string | File type: `file`, `dir`, `symlink` |
| `size` | integer | File size in bytes |
| `mode` | string | Unix file mode (e.g., `0755`) |
| `mod_time` | string | Last modification time (ISO 8601) |
| `is_link` | boolean | Whether it's a symlink |
| `link_target` | string | Symlink target path (if applicable) |

---

## Write File

Write content to a file. Creates the file and missing parent directories if needed, or atomically replaces the existing file. Each call writes the complete new content; it does not append.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/files
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | File path to write (required) |

### Request Body

Binary file content (raw bytes), up to 100 MiB. The limit is the same for running and paused sandboxes.

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Write file content
content := []byte("Hello from Sandbox0!\\n")
_, err := sandbox.WriteFile(ctx, "/workspace/hello.txt", content)
if err != nil {
    log.Fatal(err)
}
fmt.Println("File written successfully")`
    },
    {
      label: "Python",
      language: "python",
      code: `# Write file content
sandbox.write_file("/workspace/hello.txt", b"Hello from Sandbox0!\\n")
print("File written successfully")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Write file content
await sandbox.writeFile('/workspace/hello.txt', 'Hello from Sandbox0!\\n');
console.log('File written successfully');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files write /workspace/hello.txt --data "Hello from Sandbox0!" -s sb_abc123`
    }
  ]}
/>

---

## Read File

Read file content from a sandbox.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/files
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | File path to read (required) |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Read the file
readResult, err := sandbox.ReadFile(ctx, "/workspace/hello.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("File content: %s\\n", strings.TrimSpace(string(readResult)))`
    },
    {
      label: "Python",
      language: "python",
      code: `# Read file content
content = sandbox.read_file("/workspace/hello.txt")
print(f"Content: {content.decode()}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Read file content
var content = await sandbox.readFile('/workspace/hello.txt');
console.log('Content:', Buffer.from(content).toString('utf-8'));`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files cat /workspace/hello.txt -s sb_abc123

# Output:
# Hello from Sandbox0!`
    }
  ]}
/>

---

## Create Directory

Create a new directory.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/files
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | Directory path to create (required) |
| `mkdir` | boolean | Set to `true` to create directory |
| `recursive` | boolean | Create parent directories if needed |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Create directory (with parent directories)
_, err = sandbox.Mkdir(ctx, "/workspace/project/src", true)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Directory created")`
    },
    {
      label: "Python",
      language: "python",
      code: `# Create directory (with parent directories)
sandbox.mkdir("/workspace/project/src", recursive=True)
print("Directory created")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Create directory (with parent directories)
await sandbox.mkdir('/workspace/project/src', true);
console.log('Directory created');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files mkdir /workspace/project/src --parents -s sb_abc123`
    }
  ]}
/>

---

## Move File or Directory

Move or rename a file or directory.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/files/move
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `source` | string | Source path |
| `destination` | string | Destination path |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Move/rename file
_, err = sandbox.MoveFile(ctx, "/workspace/hello.txt", "/workspace/new-hello.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println("File moved")`
    },
    {
      label: "Python",
      language: "python",
      code: `# Move/rename file
sandbox.move_file("/workspace/hello.txt", "/workspace/new-hello.txt")
print("File moved")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Move/rename file
await sandbox.moveFile('/workspace/hello.txt', '/workspace/new-hello.txt');
console.log('File moved');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files mv /workspace/hello.txt /workspace/new-hello.txt -s sb_abc123`
    }
  ]}
/>

---

## Delete File or Directory

Delete a file or directory.

<Endpoint method="DELETE">
/api/v1/sandboxes/{'{id}'}/files
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | Path to delete (required) |

<Callout variant="warning">
Deleting a directory removes all its contents recursively. This action cannot be undone.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Delete file
_, err = sandbox.DeleteFile(ctx, "/workspace/new-hello.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println("File deleted")`
    },
    {
      label: "Python",
      language: "python",
      code: `# Delete file
sandbox.delete_file("/workspace/new-hello.txt")
print("File deleted")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Delete file
await sandbox.deleteFile('/workspace/new-hello.txt');
console.log('File deleted');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files rm /workspace/new-hello.txt -s sb_abc123`
    }
  ]}
/>

---

## Get File Info (Stat)

Get metadata about a file or directory.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/files/stat
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | File path to stat (required) |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Recreate the sample file after the earlier move/delete examples.
if _, err := sandbox.WriteFile(ctx, "/workspace/hello.txt", []byte("Hello from Sandbox0!\\n")); err != nil {
    log.Fatal(err)
}
// Get file info
info, err := sandbox.StatFile(ctx, "/workspace/hello.txt")
if err != nil {
    log.Fatal(err)
}
if name, ok := info.Name.Get(); ok {
    fmt.Printf("Name: %s\\n", name)
}
if size, ok := info.Size.Get(); ok {
    fmt.Printf("Size: %d bytes\\n", size)
}
if fileType, ok := info.Type.Get(); ok {
    fmt.Printf("Type: %s\\n", fileType)
}
if mode, ok := info.Mode.Get(); ok {
    fmt.Printf("Mode: %s\\n", mode)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `sandbox.write_file("/workspace/hello.txt", b"Hello from Sandbox0!\\n")
# Get file info
info = sandbox.stat_file("/workspace/hello.txt")
print(f"Name: {info.name}")
print(f"Size: {info.size} bytes")
print(f"Type: {info.type_}")
print(f"Mode: {info.mode}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await sandbox.writeFile('/workspace/hello.txt', 'Hello from Sandbox0!\\n');
// Get file info
const info = await sandbox.statFile('/workspace/hello.txt');
console.log('Name:', info.name);
console.log('Size:', info.size, 'bytes');
console.log('Type:', info.type);
console.log('Mode:', info.mode);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files write /workspace/hello.txt --data 'Hello From Sandbox0!' -s sb_abc123
s0 sandbox files stat /workspace/hello.txt -s sb_abc123`
    }
  ]}
/>

---

## List Directory

List contents of a directory.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/files/list
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | string | Directory path to list (required) |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// List directory contents
entries, err := sandbox.ListFiles(ctx, "/workspace")
if err != nil {
    log.Fatal(err)
}
for _, entry := range entries {
    path, ok := entry.Path.Get()
    if !ok {
        continue
    }
    fileType, _ := entry.Type.Get()
    size, _ := entry.Size.Get()
    fmt.Printf("- %s (%s, %d bytes)\\n", path, fileType, size)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `# List directory contents
entries = sandbox.list_files("/workspace")
for entry in entries:
    print(f"- {entry.path} ({entry.type_}, {entry.size} bytes)")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// List directory contents
var entries = await sandbox.listFiles('/workspace');
for (const entry of entries) {
    console.log(\`- \${entry.path ?? entry.name} (\${entry.type ?? 'unknown'}, \${entry.size ?? 0} bytes)\`);
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox files ls /workspace -s sb_abc123`
    }
  ]}
/>

---

## Watch Files

Watch a directory for file system changes in real-time via WebSocket. Watching requires a running sandbox and can resume compute when `auto_resume` is enabled. Offline reads and edits do not provide a watch stream.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/files/watch
</Endpoint>

### WebSocket Protocol

**Client Messages:**

| Action | Description | Example |
|--------|-------------|---------|
| `subscribe` | Subscribe to a path | `{"action": "subscribe", "path": "/tmp", "recursive": true}` |
| `unsubscribe` | Unsubscribe from a watch | `{"action": "unsubscribe", "watch_id": "watch-123"}` |

**Server Messages:**

| Type | Description | Example |
|------|-------------|---------|
| `subscribed` | Subscription confirmed | `{"type": "subscribed", "watch_id": "watch-123", "path": "/tmp"}` |
| `event` | File event occurred | `{"type": "event", "watch_id": "watch-123", "event": "write", "path": "/tmp/a.txt"}` |
| `unsubscribed` | Unsubscription confirmed | `{"type": "unsubscribed", "watch_id": "watch-123"}` |
| `error` | Error occurred | `{"type": "error", "error": "permission denied"}` |

### File Events

| Event | Description |
|-------|-------------|
| `create` | File or directory created |
| `write` | File content modified |
| `rename` | File or directory renamed |
| `chmod` | File permissions changed |
| `remove` | File or directory deleted |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Watch files for changes
watchCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

events, errs, unsubscribe, err := sandbox.WatchFiles(watchCtx, "/workspace", true)
if err != nil {
    log.Fatal(err)
}
defer func() {
    if err := unsubscribe(); err != nil {
        log.Printf("cleanup unsubscribe watch: %v", err)
    }
}()

// Process events
for {
    select {
    case ev, ok := <-events:
        if !ok {
            return
        }
        fmt.Printf("Event: %s on %s\\n", ev.Event, ev.Path)
    case err, ok := <-errs:
        if !ok {
            errs = nil
            continue
        }
        if err != nil {
            log.Printf("Watch error: %v", err)
        }
    case <-watchCtx.Done():
        fmt.Println("Watch timeout")
        return
    }
}`
    },
    {
      label: "Python",
      language: "python",
      code: `# Watch files for changes
stream = sandbox.watch_files("/workspace", recursive=True)

try:
    for event in stream.iter_events():
        print(f"Event: {event.event} on {event.path}")
        if event.event == "write":
            print(f"File modified: {event.path}")
except KeyboardInterrupt:
    pass
finally:
    stream.unsubscribe()`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Watch files for changes
const watcher = await sandbox.watchFiles('/workspace', true);

const stopTimer = setTimeout(() => {
    if (watcher.watchId) {
        watcher.unsubscribe(watcher.watchId);
    } else {
        watcher.close();
    }
}, 30000);

try {
    for await (const event of watcher.events()) {
        if (event.type === 'error') {
            console.error('Watch error:', event.error);
            break;
        }
        if (event.type === 'event') {
            console.log(\`Event: \${event.event} on \${event.path}\`);
            if (event.event === 'write') {
                console.log(\`File modified: \${event.path}\`);
            }
        }
    }
} finally {
    clearTimeout(stopTimer);
    watcher.close();
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Watch files for changes
s0 sandbox files watch /workspace --recursive -s sb_abc123`
    }
  ]}
/>

---

## Next Steps

<CardGroup>
  <Card title="SSH" href="/docs/sandbox/ssh" cta="Continue">
    Connect to sandboxes with standard SSH clients for shell and file-copy workflows.
  </Card>

  <Card title="Network" href="/docs/sandbox/network" cta="Continue">
    Configure outbound network policy, traffic rules, and protocol-aware restrictions.
  </Card>
</CardGroup>
