> ## 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.

# Suspend and Resume Workflow

> Suspend your devbox to preserve state, wait for PR feedback, and resume to continue working iteratively.

<Tip>This is an optional extension of the [Running Agents on Sandboxes](/docs/tutorials/running-agents-on-sandboxes) tutorial. Complete the main tutorial first.</Tip>

<Note title="Starter image">
  The starter image is used when you start a devbox without specifying an image, and as the default base when you build a blueprint without providing Dockerfile content. It includes:

  * **Core tools:** jq, sudo
  * **Extras:** dnsutils, iputils-ping, less, vim, rsync,
    gh
  * **Python stack:** Python 3.12, pip, uv
  * **Node stack:** Node 22.15.0, npm, Yarn 1.22.22 via
    corepack
</Note>

## Overview

Instead of shutting down the devbox after pushing a branch and recreating a fresh environment every time you get feedback, you can use Runloop's suspend and resume functionality to preserve the devbox's disk state. This allows you to respond to code review comments and make incremental changes without losing your in-progress work.

<Warning>
  Devboxes are by definition ephemeral
  environments. Please consistently snapshot your devboxes to maintain disk
  state for your projects.
</Warning>

<Steps>
  <Step title="Push branch and create a pull request">
    Push your branch to the remote repository and create a pull request for review.

    <CodeGroup>
      ```python Python theme={null}
      # Create a named shell and navigate to the project directory
      shell = devbox.shell("workflow-shell")
      await shell.exec("cd ~/sample-todo-nextjs")

      # Push the branch to the remote repository
      await shell.exec(f"git push -u origin {branch_name}")
      print(f"Pushed branch: {branch_name}")

      # Note: You'll need to create the PR manually via GitHub UI or API.
      # For this example, we'll assume you create it and get the PR number.
      # In a more automated workflow, you could use the GitHub CLI (`gh pr create`)
      # to open the PR and capture the PR number programmatically.
      pr_number = 123  # Replace with your actual PR number
      ```

      ```typescript TypeScript theme={null}
      // Create a named shell and navigate to the project directory
      const shell = devbox.shell('workflow-shell');
      await shell.exec('cd ~/sample-todo-nextjs');

      // Push the branch to the remote repository
      await shell.exec(`git push -u origin ${branchName}`);
      console.log(`Pushed branch: ${branchName}`);

      // Note: You'll need to create the PR manually via GitHub UI or API.
      // For this example, we'll assume you create it and get the PR number.
      // In a more automated workflow, you could use the GitHub CLI (`gh pr create`)
      // to open the PR and capture the PR number programmatically.
      const prNumber = 123; // Replace with your actual PR number
      ```
    </CodeGroup>
  </Step>

  <Step title="Suspend the devbox">
    Suspend the devbox to save the disk state while stopping compute costs. The devbox can be resumed later to continue working.

    <CodeGroup>
      ```python Python theme={null}
      # Suspend the devbox to preserve disk state
      await devbox.suspend()
      print(f"Devbox {devbox.id} suspended successfully")
      ```

      ```typescript TypeScript theme={null}
      // Suspend the devbox to preserve disk state
      await devbox.suspend();
      console.log(`Devbox ${devbox.id} suspended successfully`);
      ```
    </CodeGroup>

    <Warning>
      Suspended devboxes preserve all disk state, including your code changes, installed packages, and file modifications, but **in-memory state (running processes)** is lost. If you need to keep in-memory data, make sure to serialize it to disk (for example, by writing files or updating a database) before suspending. Suspended devboxes still incur storage charges until explicitly shut down.
    </Warning>
  </Step>

  <Step title="Wait for PR comment and process feedback">
    Wait for a comment on your pull request. When a comment is received, call a function to resume the devbox and process the feedback. In a production workflow, you would typically use GitHub webhooks (or at least a more robust polling loop) to detect when a comment is added; for simplicity, this example checks for comments manually in a loop.

    <CodeGroup>
      ```python Python theme={null}
      import os
      import time
      from github import Github  # Requires PyGithub library

      # Initialize GitHub client
      g = Github(os.environ["GH_TOKEN"])
      repo = g.get_repo("runloopai/sample-todo-nextjs")
      pr = repo.get_pull(pr_number)

      # Poll for new comments (in production, use webhooks instead)
      print("Waiting for PR comment...")
      last_comment_id = None
      while True:
          comments = pr.get_issue_comments()
          if comments.totalCount > 0:
              latest_comment = list(comments)[-1]
              if last_comment_id != latest_comment.id:
                  print(f"New comment: {latest_comment.body}")
                  feedback = latest_comment.body
                  # Process the feedback by resuming devbox and making changes
                  await process_feedback(devbox, feedback)
                  break
          time.sleep(10)  # Check every 10 seconds
      ```

      ```typescript TypeScript theme={null}
      import { Octokit } from '@octokit/rest';

      // Initialize GitHub client
      const octokit = new Octokit({ auth: process.env.GH_TOKEN });

      // Poll for new comments (in production, use webhooks instead)
      console.log('Waiting for PR comment...');
      let lastCommentId: number | null = null;
      while (true) {
        const { data: comments } = await octokit.rest.issues.listComments({
          owner: 'runloopai',
          repo: 'sample-todo-nextjs',
          issue_number: prNumber,
        });
        
        if (comments.length > 0) {
          const latestComment = comments[comments.length - 1];
          if (lastCommentId !== latestComment.id) {
            console.log(`New comment: ${latestComment.body}`);
            const feedback = latestComment.body || '';
            // Process the feedback by resuming devbox and making changes
            await processFeedback(devbox, feedback);
            break;
          }
        }
        await new Promise(resolve => setTimeout(resolve, 10000)); // Check every 10 seconds
      }
      ```
    </CodeGroup>

    <Note>
      For production use, prefer [GitHub webhooks](https://docs.github.com/en/webhooks) together with a [GitHub App](https://docs.github.com/en/apps) to receive PR comment events and trigger your workflow, instead of relying on a long-running polling loop as shown here.
    </Note>
  </Step>

  <Step title="Define the feedback processing function">
    Define the function that resumes the devbox and processes the PR feedback. This function handles resuming the devbox, recreating the shell, making changes based on feedback, and pushing updates.

    <CodeGroup>
      ```python Python theme={null}
      async def process_feedback(devbox, feedback: str):
          # Resume the devbox
          await devbox.resume()
          await devbox.await_running()
          print(f"Devbox {devbox.id} resumed and ready")
          
          # Recreate the named shell to continue working
          shell = devbox.shell("workflow-shell")
          await shell.exec("cd ~/sample-todo-nextjs")
          
          # Continue working based on PR feedback
          # For example, if the feedback was to adjust the blue accent color
          result = await shell.exec(
              f'claude -p "Based on the PR feedback: {feedback}"'
          )
          
          print(f"Updated based on feedback")
          print(f"Result: {await result.stdout()}")
          
          # Stage and commit the new changes
          await shell.exec("git add .")
          await shell.exec(
              f'git commit -m "Address PR feedback: {feedback[:50]}"'
          )
          
          # Push the updates
          await shell.exec("git push")
          print("Updates pushed to PR")
      ```

      ```typescript TypeScript theme={null}
      async function processFeedback(devbox: Devbox, feedback: string): Promise<void> {
        // Resume the devbox
        await devbox.resume();
        await devbox.awaitRunning();
        console.log(`Devbox ${devbox.id} resumed and ready`);
        
        // Recreate the named shell to continue working
        const shell = devbox.shell('workflow-shell');
        await shell.exec('cd ~/sample-todo-nextjs');
        
        // Continue working based on PR feedback
        // For example, if the feedback was to adjust the blue accent color
        const result = await shell.exec(
          `claude -p "Based on the PR feedback: ${feedback}"`
        );
        
        console.log('Updated based on feedback');
        console.log('Result:', await result.stdout());
        
        // Stage and commit the new changes
        await shell.exec('git add .');
        await shell.exec(
          `git commit -m "Address PR feedback: ${feedback.substring(0, 50)}"`
        );
        
        // Push the updates
        await shell.exec('git push');
        console.log('Updates pushed to PR');
      }
      ```
    </CodeGroup>

    <Note>
      You can repeat the suspend/resume cycle as many times as needed to iterate on PR feedback. The devbox preserves all your work between sessions, making it easy to pick up where you left off.
    </Note>
  </Step>
</Steps>

## Next Steps

* Learn how to [share a live preview](/docs/tutorials/running-agents-on-sandboxes/share-live-preview) of your changes in pull requests
* Explore [devbox lifecycle management](/docs/devboxes/lifecycle) for more details on suspend and resume
