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

# Resume Sandbox Work

> Restore the last session state in a new sandbox and reject writes from the old sandbox.

Use this pattern when a person or an agent must continue work in a new sandbox. The new sandbox
restores the last commit and continues on the same ephemeral branch.

This guide uses the branch layout from [Store Session State](/docs/guides/session-state).

## Load the last state

Store the repository ID, branch name, and current commit SHA in your backend. Update this record
after each successful state write.

When your backend starts a replacement worker, pass the stored values as environment variables.
`PIERRE_PRIVATE_KEY` holds the private key for your Code Storage organization. Keep this key in
trusted server code.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  import { GitStorage } from '@pierre/storage';

  const repositoryId = process.env.REPOSITORY_ID;
  const sessionId = process.env.SESSION_ID;
  const tipSha = process.env.TIP_SHA;

  const store = new GitStorage({
    key: process.env.PIERRE_PRIVATE_KEY,
    name: 'acme',
  });

  const repo = await store.findOne({ id: repositoryId });

  const sessionBranch = `sessions/${sessionId}`;
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  import os

  from pierre_storage import GitStorage

  repository_id = os.environ["REPOSITORY_ID"]
  session_id = os.environ["SESSION_ID"]
  tip_sha = os.environ["TIP_SHA"]

  storage_client = GitStorage({
      "key": os.environ["PIERRE_PRIVATE_KEY"],
      "name": "acme",
  })

  repo = await storage_client.find_one(id=repository_id)

  session_branch = f"sessions/{session_id}"
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  repositoryID := os.Getenv("REPOSITORY_ID")
  sessionID := os.Getenv("SESSION_ID")
  tipSHA := os.Getenv("TIP_SHA")

  client, err := storage.NewClient(storage.Options{
  	Key:  os.Getenv("PIERRE_PRIVATE_KEY"),
  	Name: "acme",
  })

  ctx := context.Background()
  repo, err := client.FindOne(ctx, storage.FindOneOptions{ID: repositoryID})

  sessionBranch := fmt.Sprintf("sessions/%s", sessionID)
  ```
</CodeGroup>

The current SDK has no direct branch lookup method. If your backend loses the tip SHA, call
[`listBranches()`](/docs/reference/sdk/list-branches) and follow `nextCursor` until you find the branch.

## Restore the files

Use an archive when the sandbox only needs a file tree.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  const archive = await repo.getArchiveStream({ ref: tipSha });

  // Stream archive.body to the sandbox and extract the tar.gz response.
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  archive = await repo.get_archive_stream(ref=tip_sha)

  # Stream the archive to the sandbox. Then extract the tar.gz response.
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  archive, err := repo.ArchiveStream(ctx, storage.ArchiveOptions{Ref: tipSHA})
  defer archive.Body.Close()

  // Stream the archive body to the sandbox. Then extract the tar.gz response.
  ```
</CodeGroup>

Use Git when the sandbox must create commits. Use a normal read URL to fetch a full SHA. The SHA
must remain reachable from an ephemeral ref.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  const readUrl = await repo.getRemoteURL({
    permissions: ['git:read'],
    ttl: 600,
  });
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  read_url = await repo.get_remote_url(
      permissions=["git:read"],
      ttl=600,
  )
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  readURL, err := repo.RemoteURL(ctx, storage.RemoteURLOptions{
  	Permissions: []storage.Permission{storage.PermissionGitRead},
  	TTL:         10 * time.Minute,
  })
  ```
</CodeGroup>

Pass `readUrl`, `sessionBranch`, and `tipSha` to the new sandbox as `READ_URL`, `SESSION_BRANCH`,
and `TIP_SHA`.

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
git clone --no-checkout "$READ_URL" work
git -C work fetch origin "$TIP_SHA"
git -C work switch -c "$SESSION_BRANCH" "$TIP_SHA"
```

Use the full 40-character SHA that the API returns. Do not use an abbreviated SHA or a revision
expression.

See [`getArchiveStream()`](/docs/reference/sdk/get-archive-stream) for archive filters. See
[Connect a Sandbox](/docs/guides/sandboxes#confine-a-sandbox-to-one-session-branch) for the separate push
URL.

## Reject stale writes

Write the next state only when the branch still has the tip that you restored.

<CodeGroup>
  ```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  import { readFile } from 'node:fs/promises';

  const updatedAuthSource = await readFile('src/auth.ts', 'utf8');

  const nextState = await repo
    .createCommit({
      author: { email: 'worker@example.com', name: 'Sandbox worker' },
      commitMessage: 'Resume sandbox work',
      ephemeral: true,
      expectedHeadSha: tipSha,
      targetBranch: sessionBranch,
    })
    .addFileFromString('src/auth.ts', updatedAuthSource)
    .send();

  console.log(nextState.commitSha);
  ```

  ```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  from pathlib import Path

  updated_auth_source = Path("src/auth.ts").read_text()

  next_state = await (
      repo.create_commit(
          author={"email": "worker@example.com", "name": "Sandbox worker"},
          commit_message="Resume sandbox work",
          ephemeral=True,
          expected_head_sha=tip_sha,
          target_branch=session_branch,
      )
      .add_file_from_string("src/auth.ts", updated_auth_source)
      .send()
  )

  print(next_state["commit_sha"])
  ```

  ```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
  updatedAuthSource, err := os.ReadFile("src/auth.ts")

  builder, err := repo.CreateCommit(storage.CommitOptions{
  	Author:          storage.CommitSignature{Email: "worker@example.com", Name: "Sandbox worker"},
  	CommitMessage:   "Resume sandbox work",
  	Ephemeral:       true,
  	ExpectedHeadSHA: tipSHA,
  	TargetBranch:    sessionBranch,
  })

  nextState, err := builder.
  	AddFileFromString("src/auth.ts", string(updatedAuthSource), nil).
  	Send(ctx)

  fmt.Println(nextState.CommitSHA)
  ```
</CodeGroup>

If another sandbox wrote first, this call fails with the `precondition_failed` reason. Stop the old
sandbox. Do not retry until your backend assigns a new owner.

For Git pushes, keep `no-force-push` on the session branch. An old checkout cannot replace the new
commits with a non-fast-forward push.

## Record the handoff

Store `nextState.commitSha` as the current tip. Record which person, agent, and sandbox own the
session.

Use short URL lifetimes. Mint a new write URL for the replacement sandbox. Do not reuse the old
sandbox URL.
