Skip to content

Commit 0f95f79

Browse files
committed
Preparing for v1.2.0 release
1 parent ffcb0e5 commit 0f95f79

File tree

12 files changed

+518
-82
lines changed

12 files changed

+518
-82
lines changed

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## v1.2.0
8+
9+
### Added
10+
11+
- Named volume persistence for container backend (`sbox-claude-<hash>`) to persist `.claude` folder across sessions
12+
- Claude state caching for sandbox backend
13+
- Entire `.claude` folder is synced to `.sbox/claude-cache/` on `sbox stop`
14+
- Cache is restored on sandbox creation, preserving auth across recreations
15+
- Automatically saves cache before `sbox run --recreate`
16+
- Uses `rsync` for efficient synchronization
17+
- Preserves: credentials, settings, projects, plugins, agents, shell-snapshots, todos, etc.
18+
- Platform-aware Docker socket mounting for container backend
19+
- macOS: Checks `~/.docker/run/docker.sock` first, then `/var/run/docker.sock`
20+
- Linux: Uses `/var/run/docker.sock`
21+
- `SBOX_DOCKER_SOCKET` environment variable for explicit path override
22+
- `sbox info` now shows both create and run commands for sandbox backend
23+
- `javascript` profile for JavaScript/TypeScript development (installs pnpm and yarn)
24+
- Backend-specific context instructions embedded in CLAUDE.md/AGENTS.md hierarchy
25+
- **Sandbox backend**: Documents MicroVM environment, native Docker access, persistence behavior
26+
- **Container backend**: Documents Docker socket requirements, sudo usage, volume mount caveats
27+
- Both include instructions for Docker, Docker Compose, and Testcontainers usage
28+
29+
### Changed
30+
31+
- Refactored CLI commands to use shared `WorkspaceContext` for config loading
32+
- Added `Capitalize()` method to `BackendType` for consistent display formatting
33+
- Added `SaveCache()` and `Cleanup()` methods to `Backend` interface for proper encapsulation
34+
735
## v1.1.0
836

937
### Added
@@ -29,6 +57,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2957
- Linux: Uses `/var/run/docker.sock`
3058
- `SBOX_DOCKER_SOCKET` environment variable for explicit path override
3159
- `sbox info` now shows both create and run commands for sandbox backend
60+
- `javascript` profile for JavaScript/TypeScript development (installs pnpm and yarn)
61+
- Backend-specific context instructions embedded in CLAUDE.md/AGENTS.md hierarchy
62+
- **Sandbox backend**: Documents MicroVM environment, native Docker access, persistence behavior
63+
- **Container backend**: Documents Docker socket requirements, sudo usage, volume mount caveats
64+
- Both include instructions for Docker, Docker Compose, and Testcontainers usage
3265

3366
### Changed
3467

backend_container.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (b *ContainerBackend) Run(opts BackendOptions) error {
7070
if opts.SboxFile != nil && opts.SboxFile.Config != nil {
7171
sboxFileEnvs = opts.SboxFile.Config.Envs
7272
}
73-
if err := PrepareSboxDirectory(absPath, opts.Config, opts.Config.Envs, opts.ProjectConfig.Envs, sboxFileEnvs); err != nil {
73+
if err := PrepareSboxDirectory(absPath, opts.Config, opts.Config.Envs, opts.ProjectConfig.Envs, sboxFileEnvs, BackendContainer); err != nil {
7474
return fmt.Errorf("failed to prepare .sbox directory: %w", err)
7575
}
7676

backend_sandbox.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (b *SandboxBackend) Run(opts BackendOptions) error {
5353
if opts.SboxFile != nil && opts.SboxFile.Config != nil {
5454
sboxFileEnvs = opts.SboxFile.Config.Envs
5555
}
56-
if err := PrepareSboxDirectory(opts.WorkspaceDir, opts.Config, opts.Config.Envs, opts.ProjectConfig.Envs, sboxFileEnvs); err != nil {
56+
if err := PrepareSboxDirectory(opts.WorkspaceDir, opts.Config, opts.Config.Envs, opts.ProjectConfig.Envs, sboxFileEnvs, BackendSandbox); err != nil {
5757
return fmt.Errorf("failed to prepare .sbox directory: %w", err)
5858
}
5959

cmd/sbox/stop.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ func stopE(cmd *cobra.Command, args []string) error {
8181
}
8282

8383
if info != nil {
84-
cmd.Printf("%s stopped: %s (%s)\n", ctx.BackendType.Capitalize(), info.Name, info.ID[:12])
84+
// For sandbox backend, ID and Name are the same, so don't show redundant ID
85+
if info.ID != info.Name {
86+
cmd.Printf("%s stopped: %s (%s)\n", ctx.BackendType.Capitalize(), info.Name, info.ID[:12])
87+
} else {
88+
cmd.Printf("%s stopped: %s\n", ctx.BackendType.Capitalize(), info.Name)
89+
}
8590
if removeSandbox {
8691
cmd.Printf("%s removed: %s\n", ctx.BackendType.Capitalize(), info.Name)
8792
}

embed.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sbox
2+
3+
import (
4+
_ "embed"
5+
)
6+
7+
// SandboxBackendContextMD contains instructions for Claude about the Docker Sandbox (MicroVM) environment.
8+
//
9+
//go:embed embedded/sandbox_backend.md
10+
var SandboxBackendContextMD string
11+
12+
// ContainerBackendContextMD contains instructions for Claude about the Docker Container environment.
13+
//
14+
//go:embed embedded/container_backend.md
15+
var ContainerBackendContextMD string
16+
17+
// GetBackendContextMD returns the appropriate context markdown for the given backend type.
18+
func GetBackendContextMD(backend BackendType) string {
19+
switch backend {
20+
case BackendContainer:
21+
return ContainerBackendContextMD
22+
case BackendSandbox:
23+
return SandboxBackendContextMD
24+
default:
25+
return SandboxBackendContextMD
26+
}
27+
}

embedded/container_backend.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Sandbox Environment Context (Docker Container Backend)
2+
3+
You are running inside an **sbox sandbox** using the **Docker Container** backend - a standard Docker container environment for Claude Code.
4+
5+
## Environment Overview
6+
7+
The Container backend runs your session inside a regular Docker container. This mode offers compatibility with all Docker environments and uses named volumes for persistence.
8+
9+
### User & Paths
10+
11+
- **User**: `agent` (non-root with sudo access)
12+
- **Home directory**: `/home/agent`
13+
- **Claude config**: `/home/agent/.claude` (persisted via named volume)
14+
- **Workspace**: Mounted at the same path as on the host (check `$PWD` or `$WORKSPACE_DIR`)
15+
16+
### Pre-installed Tools
17+
18+
- **Git**: Version control
19+
- **Node.js & npm**: JavaScript runtime
20+
- **rsync**: File synchronization
21+
- **curl, wget**: HTTP clients
22+
- **jq**: JSON processor
23+
- **sudo**: Elevated privileges when needed
24+
25+
Additional tools may be available depending on configured profiles.
26+
27+
## Freedom & Limitations
28+
29+
### What You Can Do
30+
31+
- Full read/write access to the workspace directory
32+
- Install packages with `sudo apt-get install`
33+
- Run any command as root with `sudo`
34+
- Access the internet (subject to firewall rules)
35+
- Run Docker commands (if `--docker-socket` was enabled)
36+
37+
### Limitations
38+
39+
- **Installed packages are ephemeral**: Lost when container is recreated
40+
- **Docker access requires explicit flag**: Must use `sbox run --docker-socket`
41+
- **Network restrictions**: Some external services may be blocked by firewall
42+
- **Docker socket permissions**: Requires `sudo` for Docker commands
43+
44+
## Persistence
45+
46+
| What | Persists Across Restarts | Persists Across Recreate |
47+
|------|-------------------------|-------------------------|
48+
| Workspace files | Yes | Yes |
49+
| Claude state (`~/.claude`) | Yes (named volume) | Yes (named volume) |
50+
| Installed apt packages | Yes | No |
51+
| Files outside workspace | Yes | No |
52+
53+
To persist environment variables:
54+
```bash
55+
echo 'export MY_VAR=value' >> /etc/profile.d/sbox-env.sh
56+
# Use login shell to load: bash -l -c "command"
57+
```
58+
59+
## Docker Usage
60+
61+
Docker access requires the `--docker-socket` flag when starting the sandbox. The Docker socket from the host is mounted into the container.
62+
63+
**Important**: Docker commands require `sudo` because the mounted socket has root permissions.
64+
65+
### Checking Docker Access
66+
67+
```bash
68+
# Verify Docker is available
69+
sudo docker info
70+
71+
# If you see "permission denied", Docker socket was not mounted
72+
# Ask the user to restart with: sbox run --docker-socket
73+
```
74+
75+
### Running Docker Containers
76+
77+
```bash
78+
# Always use sudo for Docker commands
79+
sudo docker run hello-world
80+
81+
# Run a container with port mapping
82+
sudo docker run -d -p 8080:80 nginx
83+
84+
# Access the container (localhost works because of host network)
85+
curl http://localhost:8080
86+
```
87+
88+
### Docker Compose
89+
90+
```bash
91+
# Docker Compose is available as a plugin
92+
sudo docker compose up -d
93+
sudo docker compose logs -f
94+
sudo docker compose down
95+
```
96+
97+
### Testcontainers
98+
99+
Testcontainers require the Docker socket to be mounted. Configure testcontainers to use sudo or set up Docker group permissions:
100+
101+
**Option 1: Configure DOCKER_HOST (if socket is accessible)**
102+
```bash
103+
export DOCKER_HOST=unix:///var/run/docker.sock
104+
```
105+
106+
**Option 2: Use testcontainers with root privileges**
107+
108+
For languages that spawn Docker commands, you may need to run tests as root:
109+
```bash
110+
sudo -E go test ./... # -E preserves environment
111+
sudo -E npm test
112+
```
113+
114+
**Java example:**
115+
```java
116+
// May need to run with sudo or configure Docker socket permissions
117+
@Container
118+
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
119+
```
120+
121+
**Go example:**
122+
```go
123+
// Run test with: sudo -E go test ./...
124+
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
125+
ContainerRequest: testcontainers.ContainerRequest{
126+
Image: "postgres:15",
127+
ExposedPorts: []string{"5432/tcp"},
128+
},
129+
Started: true,
130+
})
131+
```
132+
133+
**Node.js example:**
134+
```javascript
135+
// Run test with: sudo -E npm test
136+
const container = await new GenericContainer("postgres:15")
137+
.withExposedPorts(5432)
138+
.start();
139+
```
140+
141+
### Volume Mounts - Important!
142+
143+
When mounting volumes from Docker containers, you must use **host paths**, not container paths. The Docker daemon runs on the host, not inside this container.
144+
145+
```bash
146+
# WRONG - uses container path (won't work)
147+
sudo docker run -v $(pwd):/app alpine ls /app
148+
149+
# CORRECT - the workspace is mounted at the same path, so this works
150+
# because $(pwd) returns the same path as on the host
151+
sudo docker run -v ${WORKSPACE_DIR}:/app alpine ls /app
152+
```
153+
154+
If you need to mount paths outside the workspace, you'll need to know the host path.
155+
156+
## Common Patterns
157+
158+
### Installing packages (ephemeral)
159+
```bash
160+
sudo apt-get update && sudo apt-get install -y <package>
161+
```
162+
163+
### Running as root
164+
```bash
165+
sudo <command>
166+
# Or for a root shell:
167+
sudo -i
168+
```
169+
170+
### Checking if Docker socket is available
171+
```bash
172+
if [ -S /var/run/docker.sock ]; then
173+
echo "Docker socket is mounted"
174+
sudo docker ps
175+
else
176+
echo "Docker socket not available - restart with: sbox run --docker-socket"
177+
fi
178+
```
179+
180+
### Setting up Docker group (alternative to sudo)
181+
```bash
182+
# Add agent to docker group (if it exists)
183+
sudo usermod -aG docker agent
184+
# Then start a new shell to pick up the group
185+
newgrp docker
186+
# Now docker commands work without sudo
187+
docker ps
188+
```

0 commit comments

Comments
 (0)