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

# Named Shells

> Use stateful named shells to maintain environment and working directory across commands

<Tip>Start with the [Runloop Quickstart](/docs/tutorials/quickstart) to use the examples below.</Tip>

## Overview

Named shells are persistent, stateful shell sessions that maintain environment variables and the current working directory (CWD) across commands. This behavior should feel familiar: it's like using a single shell locally—when you change directories or set environment variables, those changes persist for subsequent commands.

Commands executed through the same named shell instance execute sequentially (with automatic queuing), ensuring that environment changes and directory changes from one command are preserved for the next command.

## Creating a Named Shell

Use `devbox.shell()` to create a named shell instance. You can provide a custom name, or omit it to let Runloop generate a unique shell name for you.

<CodeGroup>
  ```python Python theme={null}
  # Create a named shell with a custom name
  named_shell = devbox.shell("my-session")

  # Create a named shell with an auto-generated unique name
  uuid_shell = devbox.shell()
  ```

  ```typescript TypeScript theme={null}
  // Create a named shell with a custom name
  const namedShell = devbox.shell('my-session');

  // Create a named shell with an auto-generated unique name
  const uuidShell = devbox.shell();
  ```
</CodeGroup>

## Maintaining Working Directory

Named shells preserve the current working directory across commands. This eliminates the need to repeatedly use `cd` commands.

<CodeGroup>
  ```python Python theme={null}
  shell = devbox.shell("my-session")

  # Change directory once
  await shell.exec("cd ~/my-project")

  # All subsequent commands run in ~/my-project
  result = await shell.exec("pwd")
  print(await result.stdout())  # /home/user/my-project

  result = await shell.exec("ls -la")
  # Lists files in ~/my-project

  result = await shell.exec("git status")
  # Shows git status for ~/my-project
  ```

  ```typescript TypeScript theme={null}
  const shell = devbox.shell('my-session');

  // Change directory once
  await shell.exec('cd ~/my-project');

  // All subsequent commands run in ~/my-project
  const result = await shell.exec('pwd');
  console.log(await result.stdout());  // /home/user/my-project

  const result2 = await shell.exec('ls -la');
  // Lists files in ~/my-project

  const result3 = await shell.exec('git status');
  // Shows git status for ~/my-project
  ```
</CodeGroup>

## Maintaining Environment Variables

Named shells also preserve environment variables across commands, making it easy to set up your environment once and reuse it.

<CodeGroup>
  ```python Python theme={null}
  shell = devbox.shell("my-session")

  # Set environment variables
  await shell.exec("export NODE_ENV=production")
  await shell.exec("export API_KEY=secret123")

  # Environment variables are preserved
  result = await shell.exec("echo $NODE_ENV")
  print(await result.stdout())  # production

  result = await shell.exec("echo $API_KEY")
  print(await result.stdout())  # secret123

  # Use in subsequent commands
  await shell.exec("npm start")  # Runs with NODE_ENV=production
  ```

  ```typescript TypeScript theme={null}
  const shell = devbox.shell('my-session');

  // Set environment variables
  await shell.exec('export NODE_ENV=production');
  await shell.exec('export API_KEY=secret123');

  // Environment variables are preserved
  const result = await shell.exec('echo $NODE_ENV');
  console.log(await result.stdout());  // production

  const result2 = await shell.exec('echo $API_KEY');
  console.log(await result.stdout());  // secret123

  // Use in subsequent commands
  await shell.exec('npm start');  // Runs with NODE_ENV=production
  ```
</CodeGroup>

## Sequential Execution

Commands in a named shell execute sequentially - only one command runs at a time. This ensures that state changes from one command are fully applied before the next command starts.

<CodeGroup>
  ```python Python theme={null}
  shell = devbox.shell("my-session")

  # These commands execute one after another
  await shell.exec("cd /app")
  await shell.exec("export MY_VAR=value")
  result = await shell.exec("echo $MY_VAR")  # Will output 'value'
  result = await shell.exec("pwd")  # Will output '/app'
  ```

  ```typescript TypeScript theme={null}
  const shell = devbox.shell('my-session');

  // These commands execute one after another
  await shell.exec('cd /app');
  await shell.exec('export MY_VAR=value');
  const result = await shell.exec('echo $MY_VAR');  // Will output 'value'
  const result2 = await shell.exec('pwd');  // Will output '/app'
  ```
</CodeGroup>

## Reusing Named Shells

If you use the same shell name again, you'll reattach to the existing named shell and reuse its state. This is useful when you need to resume work in a shell session.

<CodeGroup>
  ```python Python theme={null}
  # First session
  project_shell = devbox.shell("my-session")
  await project_shell.exec("cd ~/project")
  await project_shell.exec("export PROJECT_DIR=~/project")

  # Later, reattach to the same shell
  resumed_shell = devbox.shell("my-session")
  result = await resumed_shell.exec("pwd")  # Still in ~/project
  result = await resumed_shell.exec("echo $PROJECT_DIR")  # Still set
  ```

  ```typescript TypeScript theme={null}
  // First session
  const projectShell = devbox.shell('my-session');
  await projectShell.exec('cd ~/project');
  await projectShell.exec('export PROJECT_DIR=~/project');

  // Later, reattach to the same shell
  const resumedShell = devbox.shell('my-session');
  const result = await resumedShell.exec('pwd');  // Still in ~/project
  const result2 = await resumedShell.exec('echo $PROJECT_DIR');  // Still set
  ```
</CodeGroup>

<Note>
  If two different SDK clients use the same shell name on the same devbox, they share a single underlying named shell. This can be powerful, but be careful: commands from different processes will see the same working directory and environment.
</Note>

## Streaming Output

Named shells support streaming output just like regular command execution. You can stream stdout and stderr in real-time.

<CodeGroup>
  ```python Python theme={null}
  shell = devbox.shell("my-session")

  result = await shell.exec(
    "npm install",
    stdout=lambda line: print(f"[STDOUT] {line}"),
    stderr=lambda line: print(f"[STDERR] {line}")
  )
  ```

  ```typescript TypeScript theme={null}
  const shell = devbox.shell('my-session');

  const result = await shell.exec('npm install', {
    stdout: (line: string) => console.log(`[STDOUT] ${line}`),
    stderr: (line: string) => console.error(`[STDERR] ${line}`)
  });
  ```
</CodeGroup>

## When to Use Named Shells

Use named shells when you need to:

* **Maintain working directory**: Avoid repeatedly using `cd` commands
* **Preserve environment**: Set environment variables once and reuse them
* **Run workflows in sequence**: Execute ordered commands that build on previous state
* **Resume sessions**: Reattach to a previous shell session with preserved state

For simple, isolated commands that don't depend on previous state, you can use `devbox.cmd.exec()` directly without a named shell.

<Note>
  For more information about command execution options, see the [Execute Commands](/docs/devboxes/execute-commands) documentation.
</Note>
