Skip to content

Commit 992ffce

Browse files
authored
docs: improve tutorial and edit per nv style guide (#240)
* edit tutorial per nv style guide * few more edits * iron out a bit more * rephrase prereqs * iron out policy update section * improve update policy section * nit * rebase and improve * merge the new policy update steps finalized with Kirit * Kirit's change requests
1 parent 795aa48 commit 992ffce

File tree

2 files changed

+101
-52
lines changed

2 files changed

+101
-52
lines changed

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ Release Notes <about/release-notes>
196196
:hidden:
197197
198198
Quickstart <get-started/quickstart>
199-
Tutorial: GitHub Policy Iteration <tutorials/github-sandbox>
199+
GitHub Policy Tutorial <tutorials/github-sandbox>
200200
```
201201

202202
```{toctree}

docs/tutorials/github-sandbox.md

Lines changed: 100 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tags:
1313
- Policy
1414
- Claude Code
1515
content:
16-
type: how_to
16+
type: tutorial
1717
difficulty: technical_intermediate
1818
audience:
1919
- engineer
@@ -26,7 +26,8 @@ content:
2626

2727
# Set Up a Sandbox of Claude Code with a Custom GitHub Policy
2828

29-
This tutorial walks through the iterative policy workflow. You launch a sandbox, ask Claude Code to push code to GitHub, get blocked by the default network policy, diagnose the denial from two angles — the OpenShell Terminal on your laptop and the sandbox logs from inside — and then apply a custom policy to fix it, all without recreating the sandbox.
29+
This tutorial walks through an iterative sandbox policy workflow. You launch a sandbox, ask Claude Code to push code to GitHub, and observe the default network policy denying the request.
30+
You then diagnose the denial from your machine and from inside the sandbox, apply a policy update, and verify that the policy update to the sandbox takes effect.
3031

3132
After completing this tutorial, you will have:
3233

@@ -35,35 +36,35 @@ After completing this tutorial, you will have:
3536
- Experience with the policy iteration workflow: fail, diagnose, update, verify.
3637

3738
:::{note}
38-
This tutorial shows example prompts and responses from Claude Code. The exact wording you see may differ between sessions — use the examples as a guide for the type of interaction, not as expected output.
39+
This tutorial shows example prompts and responses from Claude Code. The exact wording you see might vary between sessions. Use the examples as a guide for the type of interaction, not as expected output.
3940
:::
4041

4142
## Prerequisites
4243

4344
This tutorial requires the following:
4445

45-
- Completed the {doc}`Quickstart </get-started/quickstart>` tutorial.
46-
- A GitHub personal access token (PAT) with `repo` scope. To create one, go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens), select **Generate new token (classic)**, check the `repo` scope, and copy the token.
47-
- Your own [Anthropic account](https://console.anthropic.com/) for Claude Code. OpenShell provides the sandbox, not the agent — you need your own account to log in to Claude Code.
48-
- A public GitHub repository you own (used as the push target). A scratch or test repository works well — the tutorial pushes a small file to it. You can [create a new repository](https://github.com/new) with a README if you do not have one handy.
46+
- A working OpenShell installation. Complete the {doc}`Quickstart </get-started/quickstart>` before proceeding.
47+
- A GitHub personal access token (PAT) with `repo` scope. Generate one from the [GitHub personal access token settings page](https://github.com/settings/tokens) by selecting **Generate new token (classic)** and enabling the `repo` scope.
48+
- An [Anthropic account](https://console.anthropic.com/) with access to Claude Code. OpenShell provides the sandbox runtime, not the agent. You must authenticate with your own account.
49+
- A GitHub repository you own to use as the push target. A scratch repository is sufficient. You can [create one](https://github.com/new) with a README if needed.
4950

50-
:::{important}
51-
This tutorial uses two terminals throughout:
51+
This tutorial uses two terminals to demonstrate the iterative policy workflow:
5252

53-
- **Terminal 1 (sandbox)**The terminal where you launch the sandbox. Claude Code runs here. You interact with the agent in this terminal.
54-
- **Terminal 2 (laptop)** — A separate terminal on your laptop. You use this for `openshell term`, `openshell policy set`, and other CLI commands that manage the sandbox from the outside.
53+
- **Terminal 1**: The sandbox terminal. You create the sandbox in this terminal by running `openshell sandbox create` and interact with Claude Code inside it.
54+
- **Terminal 2**: A terminal outside the sandbox on your machine. You use this terminal for viewing the sandbox logs with `openshell term` and applying an updated policy with `openshell policy set`.
5555

5656
Each section below indicates which terminal to use.
57-
:::
5857

59-
## Launch the Sandbox
58+
## Set Up a Sandbox with Your GitHub Token
6059

61-
**Terminal 1 (sandbox)** — Create a sandbox and start Claude Code. No custom policy is needed yet — the {doc}`default policy </reference/default-policy>` is applied automatically.
60+
Depending on whether you start a new sandbox or use an existing sandbox, choose the appropriate tab and follow the instructions.
6261

6362
::::{tab-set}
6463

6564
:::{tab-item} Starting a new sandbox
6665

66+
In terminal 2, create a new sandbox with Claude Code. The {doc}`default policy </reference/default-policy>` is applied automatically, which allows read-only access to GitHub.
67+
6768
Create a {doc}`credential provider </sandboxes/providers>` that injects your GitHub token into the sandbox automatically. The provider reads `GITHUB_TOKEN` from your host environment and sets it as an environment variable inside the sandbox:
6869

6970
```console
@@ -72,42 +73,48 @@ $ openshell provider create --name my-github --type github --from-existing
7273
$ openshell sandbox create --provider my-github --keep -- claude
7374
```
7475

76+
The `--keep` flag keeps the sandbox running after Claude Code exits, so you can apply policy updates later without recreating the environment.
77+
78+
Claude Code starts inside the sandbox. It prints an authentication link. Open it in your browser, sign in to your Anthropic account, and return to the terminal. When prompted, trust the `/sandbox` workspace to allow Claude Code to read and write files.
7579
:::
7680

7781
:::{tab-item} Using an existing sandbox
7882

79-
Connect to a sandbox that is already running and set your GitHub token as an environment variable:
83+
In terminal 1, connect to a sandbox that is already running and set your GitHub token as an environment variable:
8084

8185
```console
8286
$ openshell sandbox connect <sandbox-name>
8387
$ export GITHUB_TOKEN=<your-token>
8488
```
8589

90+
To find the name of running sandboxes, run `openshell sandbox list` in terminal 2.
91+
8692
:::
8793

8894
::::
8995

90-
The `--keep` flag keeps the sandbox running after Claude Code exits, so you can apply policy updates later without recreating the environment.
91-
92-
Claude Code starts inside the sandbox. Log in through your preferred authentication method and trust the `/sandbox` workspace when prompted.
93-
9496
## Push Code to GitHub
9597

96-
**Terminal 1 (sandbox)** — Ask Claude Code to write a simple script and push it to your repository:
98+
In terminal 1, ask Claude Code to write a simple script and push it to your repository. Replace `<org>` with your GitHub organization or username and `<repo>` with your repository name.
9799

98-
```text
99-
Write a hello_world.py script and push it to https://github.com/<org>/<repo>.
100-
```
100+
:::{dropdown} Prompt
101+
:open:
102+
:icon: terminal
103+
104+
Write a `hello_world.py` script and push it to `https://github.com/<org>/<repo>`.
105+
:::
101106

102107
Claude recognizes that it needs GitHub credentials. It asks how you want to authenticate. Provide your GitHub personal access token by pasting it into the conversation. Claude configures authentication and attempts the push.
103108

104-
The push fails. Claude reports an errorbut the failure is not an authentication problem. The default sandbox policy does not permit outbound requests to GitHub, so the proxy blocks the connection before the request reaches GitHub's servers.
109+
The push fails. Claude reports an error, but the failure is not an authentication problem. The default sandbox policy permits read-only access to GitHub and blocks write operations, so the proxy denies the push before the request reaches the GitHub server.
105110

106111
## Diagnose the Denial
107112

108-
### View the logs from your laptop
113+
In this section, you diagnose the denial from your machine and from inside the sandbox.
114+
115+
### View the Logs from Your Machine
109116

110-
**Terminal 2 (laptop)** — Open a separate terminal on your laptop and launch the OpenShell Terminal:
117+
In terminal 2, launch the OpenShell terminal:
111118

112119
```console
113120
$ openshell term
@@ -128,43 +135,67 @@ l7_deny_reason: PUT /repos/<org>/<repo>/contents/hello_world.py not permitted by
128135

129136
The log shows that the sandbox proxy intercepted an outbound `PUT` request to `api.github.com` and denied it. The `github_rest_api` policy allows read operations (GET) but blocks write operations (PUT, POST, DELETE) to the GitHub API. A similar denial appears for `github.com` if Claude attempted a git push over HTTPS.
130137

131-
### Ask Claude to check the sandbox logs
138+
### Ask Claude Code to Check the Sandbox Logs
132139

133-
**Terminal 1 (sandbox)** — Switch back to Claude Code. Ask it to check the sandbox logs for denied requests:
140+
In terminal 1, ask Claude Code to check the sandbox logs for denied requests:
141+
142+
:::{dropdown} Prompt
143+
:open:
144+
:icon: terminal
134145

135-
```text
136146
Check the sandbox logs for any denied network requests. What is blocking the push?
137-
```
147+
:::
138148

139-
Claude reads the deny entries and identifies the root cause. It explains that the failure is a sandbox network policy restriction, not a token permissions issue:
149+
Claude reads the deny entries and identifies the root cause. It explains that the failure is a sandbox network policy restriction, not a token permissions issue. For example, the following is a possible response:
140150

141-
> The sandbox runs a proxy that enforces policies on outbound traffic. The
142-
> `github_rest_api` policy allows GET requests (used to read the file) but blocks
143-
> PUT/write requests to GitHub. This is a sandbox-level restriction, not a token
144-
> issue — no matter what token you provide, pushes via the API will be blocked
145-
> until the policy is updated.
151+
:::{dropdown} Response
152+
:open:
153+
:icon: comment
154+
155+
The sandbox runs a proxy that enforces policies on outbound traffic.
156+
The `github_rest_api` policy allows GET requests (used to read the file)
157+
but blocks PUT/write requests to GitHub. This is a sandbox-level restriction,
158+
not a token issue. No matter what token you provide, pushes through the API
159+
will be blocked until the policy is updated.
160+
:::
146161

147162
Both perspectives confirm the same thing: the proxy is doing its job. The default policy is designed to be restrictive. To allow GitHub pushes, you need to update the network policy.
148163

149-
Copy the deny reason from Claude's response — you will paste it into your laptop agent in the next step.
164+
Copy the deny reason from Claude's response. You paste it into an agent running on your machine in the next step.
150165

151-
## Update the Policy from Your Laptop
166+
## Update the Policy from Your Machine
152167

153-
**Terminal 2 (laptop)** — Paste the deny reason from the previous step into your coding agent (for example, Claude Code or Cursor running on your laptop) and ask it to update the sandbox policy. The deny reason gives the agent the context it needs to generate the correct policy rules.
168+
In terminal 2, paste the deny reason from the previous step into your coding agent on your machine, such as Claude Code or Cursor, and ask it to recommend a policy update. The deny reason gives the agent the context it needs to generate the correct policy rules. After pasting the following prompt sample, properly provide the GitHub organization and repository names of the repository you are pushing to.
154169

155-
The agent inspects the deny reasons, writes an updated policy that adds `github_git` and `github_api` blocks for your repository, and runs `openshell policy set` to apply it:
170+
:::{dropdown} Prompt
171+
:open:
172+
:icon: terminal
156173

157-
```console
158-
$ openshell policy set <sandbox-name> --policy /tmp/sandbox-policy-update.yaml --wait
159-
```
174+
Based on the following deny reasons, recommend a sandbox policy update that allows GitHub pushes to `https://github.com/<org>/<repo>`, and save to `/tmp/sandbox-policy-update.yaml`:
160175

161-
Your coding agent generates both the policy file and this command. Network policies are hot-reloadable — the `--wait` flag blocks until the policy engine confirms the new revision loaded, and the update takes effect immediately without restarting the sandbox or reconnecting Claude Code.
176+
The `filesystem_policy`, `landlock`, and `process` sections are static. They are read once at sandbox creation and cannot be changed by a hot-reload. They are included here for completeness so the file is self-contained, but only the `network_policies` section takes effect when you apply this to a running sandbox.
177+
:::
178+
179+
The following steps outline the expected process done by the agent:
180+
181+
1. Inspects the deny reasons.
182+
2. Writes an updated policy that adds `github_git` and `github_api` blocks that grant write access to your repository.
183+
3. Saves the policy to `/tmp/sandbox-policy-update.yaml`.
184+
185+
## Review the Generated Policy
186+
187+
Refer to the following policy example to compare with the generated policy before applying it. Confirm that the policy grants only the access you expect. In this case, `git push` operations and GitHub REST API access scoped to a single repository.
188+
189+
::::{dropdown} Full reference policy
190+
:icon: code
162191

163-
:::{dropdown} Full reference policy
192+
The following YAML shows a complete policy that extends the {doc}`default policy </reference/default-policy>` with GitHub access for a single repository. Replace `<org>` with your GitHub organization or username and `<repo>` with your repository name.
164193

165-
The following YAML shows a complete policy that extends the {doc}`default policy </reference/default-policy>` with GitHub access for a single repository. This is representative of what a coding agent generates when asked to unblock GitHub pushes. Replace `<org>` with your GitHub organization or username and `<repo>` with your repository name.
194+
The `filesystem_policy`, `landlock`, and `process` sections are static. They are read once at sandbox creation and cannot be changed by a hot-reload. They are included here for completeness so the file is self-contained, but only the `network_policies` section takes effect when you apply this to a running sandbox.
195+
196+
```{code-block} yaml
197+
:emphasize-lines: 54-100
166198
167-
```yaml
168199
version: 1
169200
170201
# ── Static (locked at sandbox creation) ──────────────────────────
@@ -311,25 +342,43 @@ The following table summarizes the two GitHub-specific blocks:
311342
| `github_git` | `github.com:443` | Git Smart HTTP protocol with TLS termination. Permits `info/refs` (clone/fetch), `git-upload-pack` (fetch data), and `git-receive-pack` (push) for the specified repository. Denies all operations on unlisted repositories. |
312343
| `github_api` | `api.github.com:443` | REST API with TLS termination. Permits all HTTP methods for the specified repository and GraphQL queries. Denies API access to unlisted repositories. |
313344

314-
The remaining blocks (`claude_code`, `nvidia_inference`, `pypi`, `vscode`) are identical to the {doc}`default policy </reference/default-policy>`. Sandbox behavior outside of GitHub operations is unchanged.
345+
The remaining blocks (`claude_code`, `nvidia_inference`, `pypi`, `vscode`) are identical to the {doc}`default policy </reference/default-policy>`. The default policy's `github_ssh_over_https` and `github_rest_api` blocks are replaced by the `github_git` and `github_api` blocks above, which grant write access to the specified repository. Sandbox behavior outside of GitHub operations is unchanged.
315346

316347
For details on policy block structure, refer to [Network Access Rules](/sandboxes/index.md#network-access-rules).
317-
:::
348+
::::
349+
350+
## Apply the Policy
351+
352+
After you have reviewed the generated policy, apply it to the running sandbox:
353+
354+
```console
355+
$ openshell policy set <sandbox-name> --policy /tmp/sandbox-policy-update.yaml --wait
356+
```
357+
358+
Network policies are hot-reloadable. The `--wait` flag blocks until the policy engine confirms the new revision loaded, and the update takes effect immediately without restarting the sandbox or reconnecting Claude Code.
318359

319360
## Retry the Push
320361

321-
**Terminal 1 (sandbox)** — Switch back to Claude Code and ask it to retry the push:
362+
In terminal 1, ask Claude Code to retry the push:
322363

323364
```text
324365
The sandbox policy has been updated. Try pushing to the repository again.
325366
```
326367

327368
The push completes successfully. The `openshell term` dashboard now shows `l7_decision=allow` entries for `api.github.com` and `github.com` where it previously showed denials.
328369

370+
## Clean Up
371+
372+
When you are finished, delete the sandbox to free cluster resources:
373+
374+
```console
375+
$ openshell sandbox delete <sandbox-name>
376+
```
377+
329378
## Next Steps
330379

331380
The following resources cover related topics in greater depth:
332381

333382
- To add per-repository access levels (read-write vs read-only) or restrict to specific API methods, refer to the [Policy Schema Reference](/reference/policy-schema.md).
334383
- To learn the full policy iteration workflow (pull, edit, push, verify), refer to {doc}`/sandboxes/policies`.
335-
- To inject credentials automatically instead of pasting tokens, refer to {doc}`/sandboxes/providers`.
384+
- To inject credentials automatically instead of pasting tokens, refer to {doc}`/sandboxes/providers`.

0 commit comments

Comments
 (0)