Skip to content

Commit 9913223

Browse files
agents-git-bot[bot]OpenCodeopencode-agent[bot]elithrar
authored
Support undefined/null as unset in setEnvVars (#27609)
* Support undefined/null as unset in environment variable APIs Document new behavior where undefined/null values in setEnvVars, exec options, and session creation now unset variables instead of throwing errors. - Add new 'Unsetting environment variables' section with examples - Update type signatures to Record<string, string | undefined> - Add examples showing undefined behavior in all env contexts - Document use cases for conditional environment setup Related to cloudflare/sandbox-sdk PR #342 * Fixed 3 minor issues across sandbox docs Co-authored-by: elithrar <elithrar@users.noreply.github.com> * Fix merge-damaged code indentation Co-authored-by: elithrar <elithrar@users.noreply.github.com> --------- Co-authored-by: OpenCode <opencode@anthropic.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: elithrar <elithrar@users.noreply.github.com> Co-authored-by: Matt Silverlock <matt@eatsleeprepeat.net>
1 parent 7a51e65 commit 9913223

5 files changed

Lines changed: 112 additions & 14 deletions

File tree

src/content/docs/sandbox/api/commands.mdx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ const result = await sandbox.exec(command: string, options?: ExecOptions): Promi
2020
```
2121

2222
**Parameters**:
23+
2324
- `command` - The command to execute (can include arguments)
2425
- `options` (optional):
2526
- `stream` - Enable streaming callbacks (default: `false`)
2627
- `onOutput` - Callback for real-time output: `(stream: 'stdout' | 'stderr', data: string) => void`
2728
- `timeout` - Maximum execution time in milliseconds
29+
- `env` - Environment variables for this command: `Record<string, string | undefined>`
30+
- `cwd` - Working directory for this command
2831
- `stdin` - Data to pass to the command's standard input (enables arbitrary input without shell injection risks)
2932

3033
**Returns**: `Promise<ExecuteResponse>` with `success`, `stdout`, `stderr`, `exitCode`
@@ -45,6 +48,15 @@ await sandbox.exec('npm install', {
4548
onOutput: (stream, data) => console.log(`[${stream}] ${data}`)
4649
});
4750
51+
// With environment variables (undefined values are skipped)
52+
await sandbox.exec('node app.js', {
53+
env: {
54+
NODE_ENV: 'production',
55+
PORT: '3000',
56+
DEBUG_MODE: undefined // Skipped, uses container default or unset
57+
}
58+
});
59+
4860
// Pass input via stdin (no shell injection risks)
4961
const result = await sandbox.exec('cat', {
5062
stdin: 'Hello, world!'
@@ -68,6 +80,7 @@ const stream = await sandbox.execStream(command: string, options?: ExecOptions):
6880
```
6981

7082
**Parameters**:
83+
7184
- `command` - The command to execute
7285
- `options` - Same as `exec()` (including `stdin` support)
7386

@@ -115,13 +128,19 @@ const process = await sandbox.startProcess(command: string, options?: ProcessOpt
115128
```
116129

117130
**Parameters**:
131+
118132
- `command` - The command to start as a background process
119133
- `options` (optional):
120134
- `cwd` - Working directory
121-
- `env` - Environment variables
135+
- `env` - Environment variables: `Record<string, string | undefined>`
122136
- `stdin` - Data to pass to the command's standard input
137+
- `timeout` - Maximum execution time in milliseconds
138+
- `processId` - Custom process ID
139+
- `encoding` - Output encoding (default: `'utf8'`)
140+
- `autoCleanup` - Whether to clean up process on sandbox sleep
123141

124142
**Returns**: `Promise<Process>` object with:
143+
125144
- `id` - Unique process identifier
126145
- `pid` - System process ID
127146
- `command` - The command being executed
@@ -178,6 +197,7 @@ await sandbox.killProcess(processId: string, signal?: string): Promise<void>
178197
```
179198

180199
**Parameters**:
200+
181201
- `processId` - The process ID (from `startProcess()` or `listProcesses()`)
182202
- `signal` - Signal to send (default: `"SIGTERM"`)
183203

@@ -218,6 +238,7 @@ const stream = await sandbox.streamProcessLogs(processId: string): Promise<Reada
218238
```
219239

220240
**Parameters**:
241+
221242
- `processId` - The process ID
222243

223244
**Returns**: `Promise<ReadableStream>` emitting `LogEvent` objects
@@ -246,6 +267,7 @@ const logs = await sandbox.getProcessLogs(processId: string): Promise<string>
246267
```
247268

248269
**Parameters**:
270+
249271
- `processId` - The process ID
250272

251273
**Returns**: `Promise<string>` with all accumulated output
@@ -341,6 +363,7 @@ await process.waitForPort(port: number, options?: WaitForPortOptions): Promise<v
341363
```
342364

343365
**Parameters**:
366+
344367
- `port` - The port number to check
345368
- `options` (optional):
346369
- `mode` - Check mode: `'http'` (default) or `'tcp'`
@@ -394,10 +417,12 @@ const result = await process.waitForLog(pattern: string | RegExp, timeout?: numb
394417
```
395418

396419
**Parameters**:
420+
397421
- `pattern` - String or RegExp to match in stdout/stderr
398422
- `timeout` - Maximum wait time in milliseconds (optional)
399423

400424
**Returns**: `Promise<WaitForLogResult>` with:
425+
401426
- `line` - The matching line of output
402427
- `matches` - Array of capture groups (for RegExp patterns)
403428

@@ -431,9 +456,11 @@ const result = await process.waitForExit(timeout?: number): Promise<WaitForExitR
431456
```
432457

433458
**Parameters**:
459+
434460
- `timeout` - Maximum wait time in milliseconds (optional)
435461

436462
**Returns**: `Promise<WaitForExitResult>` with:
463+
437464
- `exitCode` - The process exit code
438465

439466
<TypeScriptExample>

src/content/docs/sandbox/api/ports.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const response = await sandbox.exposePort(port: number, options: ExposePortOptio
2929
- `options`:
3030
- `hostname` - Your Worker's domain name (e.g., `'example.com'`). Required to construct preview URLs with wildcard subdomains like `https://8080-sandbox-abc123token.example.com`. Cannot be a `.workers.dev` domain as it doesn't support wildcard DNS patterns.
3131
- `name` - Friendly name for the port (optional)
32-
- `token` - Custom token for the preview URL (optional). Must be 1-16 characters containing only lowercase letters (a-z), numbers (0-9), hyphens (-), and underscores (_). If not provided, a random 16-character token is generated automatically.
32+
- `token` - Custom token for the preview URL (optional). Must be 1-16 characters containing only lowercase letters (a-z), numbers (0-9), hyphens (-), and underscores (\_). If not provided, a random 16-character token is generated automatically.
3333

3434
**Returns**: `Promise<ExposePortResponse>` with `port`, `url` (preview URL), `name`
3535

@@ -55,8 +55,8 @@ console.log('Stable URL:', stable.url);
5555

5656
// With custom token for stable URLs across deployments
5757
await sandbox.startProcess('node api.js');
58-
const api = await sandbox.exposePort(3000, {
59-
hostname,
58+
const api = await sandbox.exposePort(3000, {
59+
hostname,
6060
name: 'api',
6161
token: 'prod-api-v1' // URL stays same across restarts
6262
});
@@ -66,8 +66,8 @@ console.log('Stable API URL:', api.url);
6666

6767
// Multiple services with custom tokens
6868
await sandbox.startProcess('npm run dev');
69-
const frontend = await sandbox.exposePort(5173, {
70-
hostname,
69+
const frontend = await sandbox.exposePort(5173, {
70+
hostname,
7171
name: 'frontend',
7272
token: 'dev-ui'
7373
});

src/content/docs/sandbox/api/sessions.mdx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const session = await sandbox.createSession(options?: SessionOptions): Promise<E
2626
**Parameters**:
2727
- `options` (optional):
2828
- `id` - Custom session ID (auto-generated if not provided)
29-
- `env` - Environment variables for this session
29+
- `env` - Environment variables for this session: `Record<string, string | undefined>`
3030
- `cwd` - Working directory (default: `"/workspace"`)
3131

3232
**Returns**: `Promise<ExecutionSession>` with all sandbox methods bound to this session
@@ -42,7 +42,11 @@ const prodSession = await sandbox.createSession({
4242
4343
const testSession = await sandbox.createSession({
4444
id: 'test',
45-
env: { NODE_ENV: 'test', API_URL: 'http://localhost:3000' },
45+
env: {
46+
NODE_ENV: 'test',
47+
API_URL: 'http://localhost:3000',
48+
DEBUG_MODE: undefined // Skipped, not set in this session
49+
},
4650
cwd: '/workspace/test'
4751
});
4852
@@ -123,11 +127,13 @@ Deleting a session immediately terminates all running commands. The default sess
123127
Set environment variables in the sandbox.
124128

125129
```ts
126-
await sandbox.setEnvVars(envVars: Record<string, string>): Promise<void>
130+
await sandbox.setEnvVars(envVars: Record<string, string | undefined>): Promise<void>
127131
```
128132

129133
**Parameters**:
130-
- `envVars` - Key-value pairs of environment variables to set
134+
- `envVars` - Key-value pairs of environment variables to set or unset
135+
- `string` values: Set the environment variable
136+
- `undefined` or `null` values: Unset the environment variable
131137

132138
:::caution
133139
Call `setEnvVars()` **before** any other sandbox operations to ensure environment variables are available from the start.
@@ -141,7 +147,8 @@ const sandbox = getSandbox(env.Sandbox, 'user-123');
141147
await sandbox.setEnvVars({
142148
API_KEY: env.OPENAI_API_KEY,
143149
DATABASE_URL: env.DATABASE_URL,
144-
NODE_ENV: 'production'
150+
NODE_ENV: 'production',
151+
OLD_TOKEN: undefined // Unsets OLD_TOKEN if previously set
145152
});
146153
147154
// Now commands can access these variables

src/content/docs/sandbox/configuration/environment-variables.mdx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ await sandbox.setEnvVars({
3636

3737
**Use when:** You need the same environment variables for multiple commands.
3838

39+
**Unsetting variables**: Pass `undefined` or `null` to unset environment variables:
40+
41+
```typescript
42+
await sandbox.setEnvVars({
43+
API_KEY: 'new-key', // Sets API_KEY
44+
OLD_SECRET: undefined, // Unsets OLD_SECRET
45+
DEBUG_MODE: null // Unsets DEBUG_MODE
46+
});
47+
```
48+
3949
### 2. Per-command with exec() options
4050

4151
Pass environment variables for a specific command:
@@ -81,6 +91,56 @@ await session.exec("python seed.py");
8191

8292
**Use when:** You need isolated execution contexts with different environment variables running concurrently.
8393

94+
## Unsetting environment variables
95+
96+
The Sandbox SDK supports unsetting environment variables by passing `undefined` or `null` values. This enables idiomatic JavaScript patterns for managing configuration:
97+
98+
```typescript
99+
await sandbox.setEnvVars({
100+
// Set new values
101+
API_KEY: 'new-key',
102+
DATABASE_URL: env.DATABASE_URL,
103+
104+
// Unset variables (removes them from the environment)
105+
OLD_API_KEY: undefined,
106+
TEMP_TOKEN: null
107+
});
108+
```
109+
110+
**Before this change**: Passing `undefined` values would throw a runtime error.
111+
112+
**After this change**: `undefined` and `null` values run `unset VARIABLE_NAME` in the shell.
113+
114+
### Use cases for unsetting
115+
116+
**Remove sensitive data after use:**
117+
118+
```typescript
119+
// Use a temporary token
120+
await sandbox.setEnvVars({ TEMP_TOKEN: 'abc123' });
121+
await sandbox.exec('curl -H "Authorization: $TEMP_TOKEN" api.example.com');
122+
123+
// Clean up the token
124+
await sandbox.setEnvVars({ TEMP_TOKEN: undefined });
125+
```
126+
127+
**Conditional environment setup:**
128+
129+
```typescript
130+
await sandbox.setEnvVars({
131+
API_KEY: env.API_KEY,
132+
DEBUG_MODE: env.NODE_ENV === 'development' ? 'true' : undefined,
133+
PROFILING: env.ENABLE_PROFILING ? 'true' : undefined
134+
});
135+
```
136+
137+
**Reset to system defaults:**
138+
139+
```typescript
140+
// Unset to fall back to container's default NODE_ENV
141+
await sandbox.setEnvVars({ NODE_ENV: undefined });
142+
```
143+
84144
## Common patterns
85145

86146
### Pass Worker secrets to sandbox

src/content/docs/sandbox/tutorials/claude-code.mdx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Build a Worker that takes a repository URL and a task description and uses Sandb
1818
<Render file="prereqs" product="workers" />
1919

2020
You'll also need:
21+
2122
- An [Anthropic API key](https://console.anthropic.com/) for Claude Code
2223
- [Docker](https://www.docker.com/) running locally
2324

@@ -28,7 +29,9 @@ Create a new Sandbox SDK project:
2829
<PackageManagers
2930
type="create"
3031
pkg="cloudflare@latest"
31-
args={"claude-code-sandbox --template=cloudflare/sandbox-sdk/examples/claude-code"}
32+
args={
33+
"claude-code-sandbox --template=cloudflare/sandbox-sdk/examples/claude-code"
34+
}
3235
/>
3336

3437
```sh
@@ -75,8 +78,8 @@ Response:
7578

7679
```json
7780
{
78-
"logs": "Done! I've removed the brain emoji from the README title. The heading now reads \"# Cloudflare Agents\" instead of \"# 🧠 Cloudflare Agents\".",
79-
"diff": "diff --git a/README.md b/README.md\nindex 9296ac9..027c218 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,4 +1,4 @@\n-# 🧠 Cloudflare Agents\n+# Cloudflare Agents\n \n ![npm install agents](assets/npm-install-agents.svg)\n "
81+
"logs": "Done! I've removed the brain emoji from the README title. The heading now reads \"# Cloudflare Agents\" instead of \"# 🧠 Cloudflare Agents\".",
82+
"diff": "diff --git a/README.md b/README.md\nindex 9296ac9..027c218 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,4 +1,4 @@\n-# 🧠 Cloudflare Agents\n+# Cloudflare Agents\n \n ![npm install agents](assets/npm-install-agents.svg)\n "
8083
}
8184
```
8285

@@ -103,6 +106,7 @@ After first deployment, wait 2-3 minutes for container provisioning. Check statu
103106
## What you built
104107

105108
You created an API that:
109+
106110
- Accepts a repository URL and natural language task descriptions
107111
- Creates a Sandbox and clones the repository into it
108112
- Kicks off Claude Code to implement the given task

0 commit comments

Comments
 (0)