# Process Execution (/docs/process-execution)

Daytona runs shell commands and long-running processes in sandboxes. Commands run inside the sandbox, so untrusted or generated commands execute in isolation from your application.

Process execution is available through the process module of a sandbox. Every command runs as a process: you can run a command and wait for its result, or start it in the background and control it through a handle. Sessions are shell processes that keep state, such as the working directory and environment variables, across the commands that run in them. To run Python, TypeScript, and JavaScript code, see [code execution](/docs/code-execution). For interactive terminals, see [pseudo terminal (PTY)](/docs/pty). For real-time logs from long-running processes, see [log streaming](/docs/log-streaming).

<ProcessExecutionVisual />

## Use cases [#use-cases]

Process execution is used when the work in a sandbox is a shell command or a program instead of a code snippet.

- **Build and test.** Package installs, compilers, and test runners run in the sandbox, and their exit code and output return to the application. When a run times out, the result has no exit code and the command keeps running. The result holds stdout, stderr, and the output of both channels in recorded order.

- **Agent tools.** An agent runs shell commands as tools and reads their results. The commands run inside the sandbox, isolated from the application that hosts the agent. Python, TypeScript, and JavaScript snippets run through [code execution](/docs/code-execution) instead.

- **Servers and workers.** A web server or worker starts in the background, and the application checks its state, reads its logs, or stops it through its handle. By default, an unnamed process keeps its record and logs for 10 minutes after it exits, and a named process keeps them until it is deleted. A retention option overrides either default.

- **Multi-step shell work.** A session keeps the working directory and environment variables, so a sequence of commands runs as it does in one terminal. Commands in a session run one at a time and in order. A session survives pause and resume, but not stop and start.

- **Reconnects.** An application reconnects to a process it started earlier and continues to read its output. Any client with the sandbox ID and the process ID can wait for the process, read its logs, or stop it. A process name resolves only while the process runs, and the ID still works after it exits.

## Command execution [#command-execution]

### Execute commands [#execute-commands]

Run a command and wait for it to exit. The result holds the exit code and output.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

response = sandbox.process.run("ls -la")
print(response.exit_code, response.stdout)
```

| **Parameter** | **Type** | **Description**                                                                                                                                                  |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timeout` | float | Sets how many seconds `run()` waits. When the timeout elapses, `reason` is `"timed_out"`, `exit_code` is `None`, and the command keeps running. Without it, `run()` waits until the command exits. |

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `stdout` | Holds the output of the stdout channel. |
| `stderr` | Holds the output of the stderr channel. |
| `output` | Holds the output of both channels in the order it was recorded. Writes to stdout and stderr that happen close together can appear out of order. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const response = await sandbox.process.run('ls -la')
console.log(response.exitCode, response.stdout)
```

| **Parameter** | **Type** | **Description**                                                                                                                                                         |
| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timeoutSeconds` | number | Sets how many seconds `run()` waits. When the timeout elapses, `reason` is `'timed_out'`, `exitCode` is `undefined`, and the command keeps running. Without it, `run()` waits until the command exits. |

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `stdout` | Holds the output of the stdout channel. |
| `stderr` | Holds the output of the stderr channel. |
| `output` | Holds the output of both channels in the order it was recorded. Writes to stdout and stderr that happen close together can appear out of order. |

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox exec {sandboxId} -- ls -la
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Arguments after `--` run without a shell.
- To use shell syntax, wrap the command in `bash -c '...'`.
- Pass the `--timeout` flag to wait longer than its default of `10m`.
- When `--timeout` elapses, the CLI exits with code `8` and the command keeps running.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# waitMs parks the response until the process exits; values above 600000 wait 600000.
# logs=text attaches the first log page.
curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=5000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "ls -la"}'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `{toolboxProxyUrl}` is the `toolbox_proxy_url` field of the [sandbox](/docs/sandboxes#get-a-sandbox).
- When `waitMs` elapses before the process exits, the response has `state` set to `running`.
- Wait for the process and read its logs with separate requests.
- The attached log page holds up to `1000` frames, and output written in quick succession is recorded as one frame.
- Read longer output from `/processes/{processId}/logs`.
</Callout>

</TabItem>
</Tabs>

### Set the user and shell [#set-the-user-and-shell]

Run a command as a specific user or with a specific shell. The user must exist in the image and the shell must be installed.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

response = sandbox.process.run("whoami", user="nobody", shell="bash")
print(response.output)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `user` | str | Sets the user that runs the command. The user must exist in the image. |
| `shell` | str | Sets the shell that runs the command. The shell must be installed in the image. The default is `bash`, or `sh` when `bash` is not installed. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const response = await sandbox.process.run('whoami', { user: 'nobody', shell: 'bash' })
console.log(response.output)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `user` | string | Sets the user that runs the command. The user must exist in the image. |
| `shell` | string | Sets the shell that runs the command. The shell must be installed in the image. The default is `bash`, or `sh` when `bash` is not installed. |

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox exec {sandboxId} --user nobody -- bash -c whoami
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `exec` has no shell flag, so pass the shell as the program.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=5000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "whoami", "user": "nobody", "shell": "bash"}'
```

</TabItem>
</Tabs>

### Update the environment [#update-the-environment]

Set or remove environment variables for every process started in the sandbox after the update, including PTYs, sessions, and the git commands that [git operations](/docs/git-operations) run. Running processes and sessions keep their environment. Removing a variable affects only variables set through an earlier update; variables defined by the image stay.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Processes started after this call inherit the change
sandbox.update_env({"LOG_LEVEL": "debug"}, unset=["HTTP_PROXY"])

response = sandbox.process.run("echo $LOG_LEVEL")
print(response.stdout)
```

| **Parameter** | **Type**             | **Description**                                         |
| ------------- | -------------------- | ------------------------------------------------------- |
| `env` | Mapping\[str, str\] | Adds or replaces variables. Each `KEY=VALUE` entry must be smaller than 128 KiB. The sandbox's added environment, which holds the variables set at creation, by secrets, by the platform, and by all updates, is at most 1 MiB in total, counting one extra byte per variable. An update that would grow it past 1 MiB is rejected; unset variables you no longer need to make room. On Windows, the existing environment plus the update is also capped at 32,767 UTF-16 characters. On macOS, it is capped at 512 KiB, half of `ARG_MAX`, and each variable counts 9 bytes in addition to its `KEY=VALUE` text. |
| `unset` | Sequence\[str\] | Removes variables. This parameter is keyword only. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Processes started after this call inherit the change
await sandbox.updateEnv({ LOG_LEVEL: 'debug' }, { unset: ['HTTP_PROXY'] })

const response = await sandbox.process.run('echo $LOG_LEVEL')
console.log(response.stdout)
```

| **Parameter** | **Type**                 | **Description**               |
| ------------- | ------------------------ | ----------------------------- |
| `env` | Record\<string, string> | Adds or replaces variables. Each `KEY=VALUE` entry must be smaller than 128 KiB. The sandbox's added environment, which holds the variables set at creation, by secrets, by the platform, and by all updates, is at most 1 MiB in total, counting one extra byte per variable. An update that would grow it past 1 MiB is rejected; unset variables you no longer need to make room. On Windows, the existing environment plus the update is also capped at 32,767 UTF-16 characters. On macOS, it is capped at 512 KiB, half of `ARG_MAX`, and each variable counts 9 bytes in addition to its `KEY=VALUE` text. |
| `unset` | string\[\] | Removes variables. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Processes started after this call inherit the change
curl '{toolboxProxyUrl}/{sandboxId}/env' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"env": {"LOG_LEVEL": "debug"}, "unset": ["HTTP_PROXY"]}'
```

| **Field** | **Type**   | **Description**               |
| --------- | ---------- | ----------------------------- |
| `env` | object | Adds or replaces variables. Each `KEY=VALUE` entry must be smaller than 128 KiB. The sandbox's added environment, which holds the variables set at creation, by secrets, by the platform, and by all updates, is at most 1 MiB in total, counting one extra byte per variable. An update that would grow it past 1 MiB is rejected; unset variables you no longer need to make room. On Windows, the existing environment plus the update is also capped at 32,767 UTF-16 characters. On macOS, it is capped at 512 KiB, half of `ARG_MAX`, and each variable counts 9 bytes in addition to its `KEY=VALUE` text. |
| `unset` | string\[\] | Removes variables. |

</TabItem>
</Tabs>

### Execute interactive commands [#execute-interactive-commands]

Run a command that reads input. Start the command with stdin open, write input to it, and close stdin when the command has all its input.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("tr a-z A-Z", stdin="open")

# Send input to the command, then end it
handle.write_stdin("hello\n")
handle.close_stdin()

wait = handle.wait()
page = handle.logs()
for frame in page.items:
    print(f"[{frame.channel}]: {frame.data}")
print(wait.exit_code)
```

| **Parameter** | **Type** | **Description**       |
| ------------- | -------- | --------------------- |
| `stdin` | str | Sets the stdin of the command to `"open"` or `"closed"`. The default is `"closed"`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Write the input and close stdin while the command runs, because a process that has exited does not accept input.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const handle = await sandbox.process.start('tr a-z A-Z', {
    stdin: 'open',
})

// Send input to the command, then end it
await handle.writeStdin('hello\n')
await handle.closeStdin()

const wait = await handle.wait()
const page = await handle.logs()
for (const frame of page.items) {
    console.log(`[${frame.channel}]:`, frame.data)
}
console.log(wait.exitCode)
```

| **Parameter** | **Type** | **Description**       |
| ------------- | -------- | --------------------- |
| `stdin` | string | Sets the stdin of the command to `'open'` or `'closed'`. The default is `'closed'`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Write the input and close stdin while the command runs, because a process that has exited does not accept input.
</Callout>

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Piped input is written to the command's stdin, followed by end of input
printf 'hello\n' | daytona sandbox exec {sandboxId} -- tr a-z A-Z
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Start a command with stdin open
curl '{toolboxProxyUrl}/{sandboxId}/processes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "kind": "exec",
  "shellCommand": "tr a-z A-Z",
  "stdin": "open"
}'

# Send input to the process and close stdin
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/stdin' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
  "data": "hello\n",
  "eof": true
}'

# Get process logs
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field** | **Type** | **Description**     |
| --------- | -------- | ------------------- |
| `stdin` | string | Sets the stdin of the command to `open` or `closed`. The default is `closed`. |

| **Field** | **Type**  | **Description**                       |
| --------- | --------- | ------------------------------------- |
| `eof` | boolean | Closes stdin after writing `data` when set to `true`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Send the last input with `eof` set to `true` while the command runs, because a process that has exited does not accept input.
</Callout>

</TabItem>
</Tabs>

## Process control [#process-control]

### Start a background process [#start-a-background-process]

Start a process without waiting for it to exit. The SDK returns a handle that you can use to wait for, read logs from, and stop the process.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("python -m http.server 8000", tag="web")
print(handle.id)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `tag` | str | Groups the process with others for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). The tag is 1 to 128 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, and `-`, and is not `.` or `..`. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const handle = await sandbox.process.start('python -m http.server 8000', { tag: 'web' })
console.log(handle.id)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `tag` | string | Groups the process with others for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). The tag is 1 to 128 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, and `-`, and is not `.` or `..`. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Start a detached process (no waitMs). Returns 201 with the process record.
curl '{toolboxProxyUrl}/{sandboxId}/processes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "python -m http.server 8000", "tag": "web"}'
```

| **Field**  | **Type** | **Description**                                                                                                                                   |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keepLogs` | string | Sets how long the record and logs are kept after the process exits. With `on_exit_ttl`, the record and logs are removed 10 minutes after the process exits. The default is `on_exit_ttl` without `name` and `until_cleanup` with `name`. |
| `tag` | string | Groups the process with others for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). The tag is 1 to 128 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, and `-`, and is not `.` or `..`. It applies to `exec`, `pty`, and `session` processes. A `code` process cannot have a tag, and a command in a session takes the tag of the session. |

</TabItem>
</Tabs>

### List processes [#list-processes]

List the processes in a sandbox. Filter the list by state, kind, session, name, PID, or tag.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

running = sandbox.process.list(state="running")
for info in running.items:
    print(info.id, info.name, info.state, info.pid)

# Only the processes started with the tag
web = sandbox.process.list(tag="web")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const running = await sandbox.process.list({ state: 'running' })
for (const info of running.items) {
    console.log(info.id, info.name, info.state, info.pid)
}

// Only the processes started with the tag
const web = await sandbox.process.list({ tag: 'web' })
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Filters: --state running|terminal|all, --kind, --session-id, --name, --pid
daytona sandbox process list {sandboxId} --state running
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Filters: state=running|terminal|all, kind, sessionId, name, pid, tag
curl '{toolboxProxyUrl}/{sandboxId}/processes?state=running' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Process states [#process-states]

| **State**          | **Meaning**                                                                                           |
| ------------------ | ----------------------------------------------------------------------------------------------------- |
| **`starting`**     | The record exists; the process has not been spawned yet.                                              |
| **`provisioning`** | [Code execution](/docs/code-execution) only: the language kernel is being installed or started. Progress is reported on the `setup` log channel. |
| **`running`**      | The process has a PID and is executing.                                                               |
| **`terminal`**     | The process has finished. `reason`, `exitCode`, and `signal` describe how.                            |

| **Reason**      | **Meaning**                                                                                                   |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| **`exited`**    | Exited on its own; `exitCode` is set. A code execution that raised an exception exits with `1`.               |
| **`signaled`**  | Killed by a signal; `signal` is set. `wait` reports bare names (`TERM`, `KILL`), the API reports `SIGTERM`, `SIGKILL`. |
| **`timed_out`** | The `timeoutMs` set at start elapsed; the process was killed with `SIGKILL`. Also returned by `wait` itself when its own long-poll window elapses first, with no `exitCode`. |
| **`lost`**      | The process disappeared without the daemon observing its exit, for example after a sandbox stop and start, or when its session was deleted while it was still running. |
| **`failed`**    | The process could not be started or ran into a daemon-side error.                                             |

### Log retention [#log-retention]

Choose how long a process record and its logs are kept after the process exits. The policy is set when the process starts. Once a record is removed, the process can no longer be waited on or read. See [log streaming](/docs/log-streaming#log-retention) for size caps and eviction.

| **Policy**          | **Behavior**                                                                                    |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| **`until_cleanup`** | Kept until the process is deleted (`DELETE /processes/{id}`). Default for API requests that set `name`; the SDK `pty` namespace also uses it. |
| **`on_exit_ttl`**   | Kept for 10 minutes after exit, then removed. Default for API requests without `name` and for the SDK `start` and `run` methods; `run` does not accept `none`. |
| **`none`**          | Logs are discarded as soon as the process exits and can no longer be read. The record is kept for 10 minutes, so `wait` still returns the exit result. |

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Keep the record and logs until delete() is called
handle = sandbox.process.start("pip list", keep_logs="until_cleanup")
handle.wait()

for frame in handle.logs().items:
    print(frame.data, end="")
handle.delete()
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `keep_logs` | str | Sets how long the record and logs are kept after the process exits: `"until_cleanup"`, `"on_exit_ttl"`, or `"none"`. The default is `"on_exit_ttl"`. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Keep the record and logs until delete() is called
const handle = await sandbox.process.start('pip list', { keepLogs: 'until_cleanup' })
await handle.wait()

for (const frame of (await handle.logs()).items) {
    process.stdout.write(frame.data)
}
await handle.delete()
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `keepLogs` | string | Sets how long the record and logs are kept after the process exits: `'until_cleanup'`, `'on_exit_ttl'`, or `'none'`. The default is `'on_exit_ttl'`. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Keep the record and logs until DELETE
curl '{toolboxProxyUrl}/{sandboxId}/processes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "pip list", "keepLogs": "until_cleanup"}'

# Read logs; limit defaults to 1000, and values outside 1 to 5000 are clamped to that range
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Remove the record and logs
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Wait for a process [#wait-for-a-process]

Wait for a process to finish, or until a timeout elapses.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("pip install requests")

# Wait up to 2 minutes; without timeout, wait() blocks until the process is terminal
result = handle.wait(timeout=120)
print(result.reason, result.exit_code)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const handle = await sandbox.process.start('pip install requests')

// Wait up to 2 minutes; without timeout, wait() blocks until the process is terminal
const result = await handle.wait({ timeoutSeconds: 120 })
console.log(result.reason, result.exitCode)
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Wait up to 2 minutes; exits with the process exit code, 128 plus the signal number if a
# signal stopped it (130 for INT, 143 for TERM, 137 for KILL), or 8 if the timeout elapses
# first or the process hit its own timeout
daytona sandbox process wait {sandboxId} {processId} --timeout 2m
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Long-poll for the terminal state. Omitting timeoutMs waits 60 s; 0 waits until terminal.
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/wait?timeoutMs=120000' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Response when the process exits:
# {"exitCode": 0, "reason": "exited"}
# Response when the wait window elapses first:
# {"reason": "timed_out"}
```

</TabItem>
</Tabs>

### Stop a process [#stop-a-process]

Stop a running process by sending it a signal. The SDKs send the terminate signal.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("python -m http.server 8000")

# Sends SIGTERM
handle.kill()
```

| **Parameter**    | **Type**     | **Description**                                                                                |
| ---------------- | ------------ | ---------------------------------------------------------------------------------------------- |
| `signal` | SignalName | Sets the signal to send: `TERM`, `KILL`, `INT`, `HUP`, `QUIT`, `USR1`, or `USR2`. The default is `"TERM"`. |
| `escalate_after` | float | Sends `escalate_to` if the process is still running after this many seconds. The value is at most `600`. |
| `escalate_to` | SignalName | Sets the signal sent after `escalate_after`. The default is `"KILL"`. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const handle = await sandbox.process.start('python -m http.server 8000')

// Sends SIGTERM
await handle.kill()
```

| **Parameter**   | **Type**        | **Description**                                                                                        |
| --------------- | --------------- | ------------------------------------------------------------------------------------------------------ |
| `signal` | ProcessSignal | Sets the signal to send: `'TERM'`, `'KILL'`, `'INT'`, `'HUP'`, `'QUIT'`, `'USR1'`, or `'USR2'`. |
| `escalateAfterSeconds` | number | Sends `escalateTo` if the process is still running after this many seconds. The value is at most `600`. |
| `escalateTo` | ProcessSignal | Sets the signal sent after `escalateAfterSeconds`. The default is `'KILL'`. |

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox process signal {sandboxId} {processId} TERM

# Send KILL
daytona sandbox process kill {sandboxId} {processId}
```

| **Flag**           | **Type** | **Description**                                                                                                 |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------- |
| `--escalate-after` | string | Sends `--escalate-to` if the process is still running after this delay. The value is a duration such as `5s`, at most `10m`. |
| `--escalate-to` | string | Sets the second signal, sent after `--escalate-after`. The default is `KILL`. This flag works only together with `--escalate-after`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Signal names have no `SIG` prefix, and the accepted names are `TERM`, `KILL`, `INT`, `HUP`, `QUIT`, `USR1`, and `USR2`.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/signals' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"signal": "SIGTERM"}'
```

| **Field**         | **Type**  | **Description**                                                                        |
| ----------------- | --------- | -------------------------------------------------------------------------------------- |
| `signal` | string | Sets the signal to send, with the `SIG` prefix. An empty value sends `SIGTERM`. |
| `escalateAfterMs` | integer | Sends `escalateTo` if the process is still running after this delay. The value is at most `600000`. |
| `escalateTo` | string | Sets the signal sent after `escalateAfterMs`. The default is `SIGKILL`. |

</TabItem>
</Tabs>

### Stop processes by tag [#stop-processes-by-tag]

Stop every process started with a tag, and everything those processes started, including descendants that detached and the survivors of tagged processes that already exited. The call returns once none of them is running, with the IDs of the tagged processes it stopped. Processes whose records were deleted or expired are stopped too but not listed, so an empty list does not mean nothing was stopped. The call does not delete records or logs; they are kept for as long as the `keepLogs` setting of each process allows. The tag can be used again right after the call.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

sandbox.process.start("python -m http.server 8000", tag="web")
sandbox.process.start("python -m http.server 8001", tag="web")

stopped = sandbox.process.kill_all(tag="web", timeout=30)
print(stopped)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `tag` | str | Sets the tag of the processes to stop. This parameter is required. |
| `timeout` | float | Sets how many seconds to wait for the processes to exit, more than `0` and at most `60`. Fractions are allowed. A value outside the range raises `BadRequestError` before any request is sent. The default is `10`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- macOS sandboxes, and Linux sandboxes without process containment, cannot stop processes by tag.
- When processes are still running at the timeout, or another start or stop under the tag is in progress, the call fails. Calling it again is safe.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

await sandbox.process.start('python -m http.server 8000', { tag: 'web' })
await sandbox.process.start('python -m http.server 8001', { tag: 'web' })

const stopped = await sandbox.process.killAll({ tag: 'web', timeoutSeconds: 30 })
console.log(stopped)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `tag` | string | Sets the tag of the processes to stop. This parameter is required. |
| `timeoutSeconds` | number | Sets how many seconds to wait for the processes to exit, more than `0` and at most `60`. A value outside the range throws `BadRequestError` before any request is sent. The default is `10`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- macOS sandboxes, and Linux sandboxes without process containment, cannot stop processes by tag.
- When processes are still running at the timeout, or another start or stop under the tag is in progress, the call fails. Calling it again is safe.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes:kill' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"tag": "web", "timeoutMs": 30000}'
```

| **Field** | **Type** | **Description** |
| --------- | -------- | --------------- |
| `tag` | string | Sets the tag of the processes to stop. This field is required. |
| `timeoutMs` | integer | Sets how many milliseconds to wait for the processes to exit, from `1` to `60000`. The default is `10000`. |

The response has the following fields:

| **Field** | **Description** |
| --------- | --------------- |
| `processIds` | The tagged processes that were running when the call started, and exited tagged processes that still had descendants running. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- macOS sandboxes, and Linux sandboxes without process containment, cannot stop processes by tag.
- When processes are still running at `timeoutMs`, or another start or stop under the tag is in progress, the request fails. Repeating it is safe.
</Callout>

</TabItem>
</Tabs>

### Reconnect to a process [#reconnect-to-a-process]

Reconnect to a process from another worker or process. Processes are addressed by ID, so any client with the sandbox ID and the process ID can wait for, read logs from, and stop the process. To reconnect to an interpreter context, see [reconnect to a context](/docs/code-execution#reconnect-to-a-context).

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Worker A: start the process and persist the handle
handle = sandbox.process.start("pip install requests")
saved = handle.to_json()  # {"sandboxId": "...", "processId": "...", "kind": "exec"}

# Worker B: reconnect and wait for the same process
handle = sandbox.process.connect(saved)
result = handle.wait()
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `connect()` takes the saved dict or a process ID.
- A process name resolves only while the process runs.
- After the process exits, reconnect by ID.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Worker A: start the process and persist the handle
const handle = await sandbox.process.start('pip install requests')
const saved = handle.toJSON() // { sandboxId: '...', processId: '...', kind: 'exec' }

// Worker B: reconnect and wait for the same process
const reconnected = sandbox.process.connect(saved)
const result = await reconnected.wait()
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `connect()` takes the saved object or a process ID.
- A process name resolves only while the process runs.
- After the process exits, reconnect by ID.
</Callout>

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# --timeout defaults to 10m; raise it for long-running processes
daytona sandbox process wait {sandboxId} {processId} --timeout 1h

# Attach to the live output stream
daytona sandbox process attach {sandboxId} {processId}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}/wait' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field**   | **Type**  | **Description**                                                                                                                                                 |
| ----------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timeoutMs` | integer | Sets how long the request waits for the process to exit. Without it, the request returns after 60 seconds with `{"reason": "timed_out"}` if the process is still running. Repeat the request, or pass `0` to wait until the process exits. |

</TabItem>
</Tabs>

### Entrypoint process [#entrypoint-process]

Get the details and logs of the sandbox entrypoint. It runs as a system process, so you can read it like any other process but not signal, write to, or delete it.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Entrypoint process details
info = sandbox.process.get("entrypoint")
print(f"Command: {info.argv or info.shell_command}")

# Entrypoint logs
handle = sandbox.process.connect("entrypoint")
for frame in handle.logs().items:
    print(f"[{frame.channel}]: {frame.data}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Entrypoint process details
const info = await sandbox.process.get('entrypoint')
console.log('Command:', info.argv ?? info.shellCommand)

// Entrypoint logs
const handle = sandbox.process.connect('entrypoint')
for (const frame of (await handle.logs()).items) {
    console.log(`[${frame.channel}]:`, frame.data)
}
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Get entrypoint process details
daytona sandbox process get {sandboxId} entrypoint

# Get entrypoint logs
daytona sandbox process logs {sandboxId} entrypoint

# Control-plane entrypoint logs
daytona sandbox logs {sandboxId} --entrypoint
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Get entrypoint process details
curl '{toolboxProxyUrl}/{sandboxId}/processes/entrypoint' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Get entrypoint logs
curl '{toolboxProxyUrl}/{sandboxId}/processes/entrypoint/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Control-plane entrypoint logs
curl 'https://api.daytona.com/sandboxes/{sandboxId}/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

## Session operations [#session-operations]

A session is a long-lived shell that runs the commands you send to it. Commands in a session run one at a time, in order, and share the shell state, such as the working directory, exported variables, and functions. To run commands in parallel, use separate sessions or background processes.

A session is a long-running shell process, and each command in it is an ordinary process with its own logs, exit code, timeout, and signals. Interrupting a command stops only that command, and the session keeps running. Sessions survive pause and resume, but not stop and start. [Interpreter contexts](/docs/code-execution#interpreter-contexts) run a language kernel instead of a shell.

### Create a session [#create-a-session]

Create a shell session. The working directory and environment variables of the session apply to all of its commands.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

session = sandbox.process.create_session(cwd="/tmp", env={"CI": "1"}, name="build-session")
print(session.id)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `cwd` | str | Sets the working directory of the shell. The default is the image's `WORKDIR`. When the image declares none, the default is the home directory of the `user` set at sandbox creation, or the daemon working directory when no user is set or the home directory does not exist. On macOS, the default is the home directory of that user. |
| `env` | Mapping\[str, str\] | Sets environment variables applied when the shell starts. |
| `shell` | str | Sets the path or name of the shell. On Linux and macOS, it must be a POSIX shell: `bash`, `sh`, `dash`, `zsh`, or `ksh`. The shell must be installed in the image. |
| `login` | bool | Starts a login shell when set to `True`. |
| `name` | str | Sets a name that commands can use in place of the session ID while the session is running. Names are unique among running processes. |
| `tag` | str | Sets the tag of the session. Every command in the session takes it, for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const session = await sandbox.process.createSession({ cwd: '/tmp', env: { CI: '1' }, name: 'build-session' })
console.log(session.id)
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `cwd` | string | Sets the working directory of the shell. The default is the image's `WORKDIR`. When the image declares none, the default is the home directory of the `user` set at sandbox creation, or the daemon working directory when no user is set or the home directory does not exist. On macOS, the default is the home directory of that user. |
| `env` | Record\<string, string> | Sets environment variables applied when the shell starts. |
| `shell` | string | Sets the path or name of the shell. On Linux and macOS, it must be a POSIX shell: `bash`, `sh`, `dash`, `zsh`, or `ksh`. The shell must be installed in the image. |
| `login` | boolean | Starts a login shell when set to `true`. |
| `name` | string | Sets a name that commands can use in place of the session ID while the session is running. Names are unique among running processes. |
| `tag` | string | Sets the tag of the session. Every command in the session takes it, for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "session", "cwd": "/tmp", "env": {"CI": "1"}, "name": "build-session"}'
```

| **Field** | **Type**  | **Description** |
| --------- | --------- | --------------- |
| `kind` | string | Sets the kind to `session`. |
| `cwd` | string | Sets the working directory of the shell. The default is the image's `WORKDIR`. When the image declares none, the default is the home directory of the `user` set at sandbox creation, or the daemon working directory when no user is set or the home directory does not exist. On macOS, the default is the home directory of that user. |
| `env` | object | Sets environment variables applied when the shell starts. |
| `shell` | string | Sets the path or name of the shell. On Linux and macOS, it must be a POSIX shell: `bash`, `sh`, `dash`, `zsh`, or `ksh`. The shell must be installed in the image. |
| `login` | boolean | Starts a login shell when set to `true`. The default is `false`. |
| `user` | string | Sets the user that the shell runs as. |
| `name` | string | Sets a name that commands can use in place of the session ID while the session is running. Names are unique among running processes. |
| `tag` | string | Sets the tag of the session. Every command in the session takes it, for [listing](#list-processes) and [stopping processes by tag](#stop-processes-by-tag). |

The response has the following fields:

| **Field** | **Description**                                                         |
| --------- | ----------------------------------------------------------------------- |
| `id`      | The session ID. Commands pass it as `sessionId` to run in the session.  |
| `state`   | The lifecycle state: `starting`, `provisioning`, `running`, or `terminal`. |
| `cwd`     | The current directory of the session shell.                             |

</TabItem>
</Tabs>

### Execute session commands [#execute-session-commands]

Run commands in a session. Shell state set by one command is available to the next. A command timeout starts when the command starts, after any commands queued ahead of it. When the timeout elapses, Daytona stops the command and the session keeps running.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

with sandbox.process.create_session() as session:
    # Shell state persists between commands
    session.run("cd /tmp && export STAGE=dev")
    result = session.run("pwd && echo $STAGE", execution_timeout=30)
    print(result.stdout)

    # Interrupt one command; the session keeps running
    command = session.start("sleep 600")
    command.kill(signal="INT")
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `timeout` | float | Sets how many seconds `run()` waits for the command. |
| `execution_timeout` | float | Sets how many seconds the command can run before Daytona stops it. The session keeps running. |
| `name` | str | Sets a name for the command process. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

await using session = await sandbox.process.createSession()

// Shell state persists between commands
await session.run('cd /tmp && export STAGE=dev')
const result = await session.run('pwd && echo $STAGE', { executionTimeoutSeconds: 30 })
console.log(result.stdout)

// Interrupt one command; the session keeps running
const command = await session.start('sleep 600')
await command.kill({ signal: 'INT' })
```

| **Parameter** | **Type** | **Description** |
| ------------- | -------- | --------------- |
| `timeoutSeconds` | number | Sets how many seconds `run()` waits for the command. |
| `executionTimeoutSeconds` | number | Sets how many seconds the command can run before Daytona stops it. The session keeps running. |
| `name` | string | Sets a name for the command process. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Shell state persists between commands
curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=60000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "cd /tmp && export STAGE=dev", "sessionId": "build-session"}'

curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=60000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "pwd && echo $STAGE", "sessionId": "build-session", "timeoutMs": 30000}'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- A command with `sessionId` must be `kind: "exec"` with `shellCommand`, and `sessionId` takes the session ID or its name.
- A command with `sessionId` cannot set `argv`, `cwd`, `env`, `shell`, `login`, or `user`, because the settings of the session apply.
</Callout>

</TabItem>
</Tabs>

### Reconnect to a session [#reconnect-to-a-session]

Reconnect to a session from another worker or process. Sessions are addressed by ID, so any client with the sandbox ID and the session ID can run commands in the session and read its status. Reconnecting sends no request; the next call on the session does.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Worker A: create the session and persist it
session = sandbox.process.create_session()
session.run("cd /tmp")
saved = session.to_json()  # {"sandboxId": "...", "sessionId": "..."}

# Worker B: reconnect and run in the same shell
session = sandbox.process.connect_session(saved)
result = session.run("pwd")
```

<Callout type="note" title="Notes">
- `connect_session()` takes the saved dict or a session ID.
- Reconnect through the process module of the sandbox the session belongs to; a saved session of another sandbox is refused before any request.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Worker A: create the session and persist it
const session = await sandbox.process.createSession()
await session.run('cd /tmp')
const saved = session.toJSON() // { sandboxId: '...', sessionId: '...' }

// Worker B: reconnect and run in the same shell
const reconnected = sandbox.process.connectSession(saved)
const result = await reconnected.run('pwd')
```

<Callout type="note" title="Notes">
- `connectSession()` takes the saved object or a session ID.
- Reconnect through the process module of the sandbox the session belongs to; a saved session of another sandbox is refused before any request.
</Callout>

</TabItem>
</Tabs>

### Get session status [#get-session-status]

Get the status of a session, including the current working directory of its shell, and list the commands that ran in it.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()
session.run("cd /tmp")

info = session.get()
print(info.state, info.cwd)

listed = sandbox.process.list(session_id=session.id)
for proc in listed.items:
    print(f"Process: {proc.id}, Command: {proc.shell_command}, Exit Code: {proc.exit_code}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()
const session = await sandbox.process.createSession()
await session.run('cd /tmp')

const info = await session.get()
console.log(info.state, info.cwd)

const listed = await sandbox.process.list({ sessionId: session.id })
for (const proc of listed.items) {
  console.log(`Process: ${proc.id}, Command: ${proc.shellCommand}, Exit Code: ${proc.exitCode}`)
}
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Get the session process
daytona sandbox process get {sandboxId} {sessionId}

# List processes in a session
daytona sandbox process list {sandboxId} --session-id {sessionId}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Get the session process
curl '{toolboxProxyUrl}/{sandboxId}/processes/{sessionId}' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# List processes in a session
curl '{toolboxProxyUrl}/{sandboxId}/processes?sessionId={sessionId}' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### List sessions [#list-sessions]

List the shell sessions in a sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()

sessions = sandbox.process.list(kind="session")
for proc in sessions.items:
    print(proc.id, proc.state)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()
const session = await sandbox.process.createSession()

const sessions = await sandbox.process.list({ kind: 'session' })
for (const proc of sessions.items) {
  console.log(proc.id, proc.state)
}
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox process list {sandboxId} --kind session
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes?kind=session' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Get session command [#get-session-command]

Get a command that ran in a session by its process ID.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()
session.run("echo hello")

listed = sandbox.process.list(session_id=session.id)
command = sandbox.process.get(listed.items[0].id)
print(f"Command: {command.shell_command}, Exit Code: {command.exit_code}")
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()
const session = await sandbox.process.createSession()
await session.run('echo hello')

const listed = await sandbox.process.list({ sessionId: session.id })
const command = await sandbox.process.get(listed.items[0].id)
console.log(`Command: ${command.shellCommand}, Exit Code: ${command.exitCode}`)
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox process get {sandboxId} {processId}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

### Delete a session [#delete-a-session]

Delete a session. This ends its shell, stops its running commands, and removes the records of its commands.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()

session.delete()
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()
const session = await sandbox.process.createSession()

await session.delete()
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes/{sessionId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

## Resource management [#resource-management]

A process kept with `until_cleanup` keeps its record and logs until you delete it. The API applies this policy to processes started with a `name`; the SDKs apply it only when you pass it. Delete such a process when you no longer need it; deleting stops the process if it is still running.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("sleep 3600", name="my-process", keep_logs="until_cleanup")
try:
    pass  # Do work...
finally:
    handle.delete()  # kills the process if it is still running
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk';

const daytona = new Daytona();
const sandbox = await daytona.v2.create();

const handle = await sandbox.process.start("sleep 3600", { name: "my-process", keepLogs: "until_cleanup" });
try {
    // Do work...
} finally {
    await handle.delete(); // kills the process if it is still running
}
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Delete a process record and its logs; a running process is killed first
curl '{toolboxProxyUrl}/{sandboxId}/processes/{processId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>

## Permissions [#permissions]

Process execution runs through the toolbox of a sandbox. Access is governed by one project permission, which you can grant to an [organization role](/docs/organizations#roles) or an [API key](/docs/api-keys).

A [sandbox access token](/docs/sandboxes#create-an-access-token) with the toolbox:exec operation grants the same access to a single sandbox, without an API key. The sandbox read permission alone does not grant toolbox access.

| **Permission**        | **Grants**                                                        |
| --------------------- | ----------------------------------------------------------------- |
| **`write:sandboxes`** | Run commands, processes, and sessions, and update the environment |
