Problem
When querying sandbox metadata via GET /sandboxes/{sandboxID} (packages/api/internal/handlers/sandbox_get.go), paused or stopped sandboxes loaded from lastSnapshot exhibit two payload inconsistencies with the OpenAPI specification and list endpoints:
-
Missing VolumeMounts in Paused Sandbox Response:
When a sandbox is paused, lastSnapshot.Snapshot.Config.VolumeMounts contains all persistent volume mount mappings. However, GetSandboxesSandboxID constructs api.SandboxDetail without populating the VolumeMounts field, resulting in "volume_mounts": null or omission in the JSON response. In contrast, the list endpoint GET /v2/sandboxes (packages/api/internal/handlers/sandboxes_list.go:L292) correctly maps VolumeMounts for paused sandboxes via convertFromDBMountsToAPIMounts.
-
Malformed Alias Serialized as "" (Empty String) instead of null / Omission:
When a paused sandbox has no aliases, pausedAlias := firstAlias(lastSnapshot.Aliases) returns "" (empty string). sandbox.Alias = &pausedAlias then assigns a non-nil pointer to "". When serialized to JSON, this emits "alias": "" instead of null or omitting the property, violating the OpenAPI schema contract for optional string fields.
-
Potential Nil Pointer Panic on a.orchestrator:
GetSandboxesSandboxID attempts to call a.orchestrator.GetSandbox without verifying a.orchestrator != nil, causing panics in isolated unit tests and environments where the orchestrator client is nil.
Root Cause
In packages/api/internal/handlers/sandbox_get.go:L235-L265:
// Current implementation for paused sandboxes:
pausedAlias := firstAlias(lastSnapshot.Aliases)
sandbox = api.SandboxDetail{
// VolumeMounts is omitted completely
Alias: &pausedAlias, // Points to "" when len(Aliases) == 0
...
}
Comparison of metadata mapping across endpoints:
| Endpoint |
Paused Sandbox VolumeMounts |
Empty Alias Representation |
GET /v2/sandboxes (sandboxes_list.go) |
Populated via convertFromDBMountsToAPIMounts |
nil (omitted) |
GET /sandboxes/{id} (Current) |
nil (omitted) |
&"" (serialized as "") |
GET /sandboxes/{id} (Expected) |
Populated from Config.VolumeMounts |
nil (omitted) |
Reproduction Steps
- Create a sandbox with persistent volume mounts:
POST /sandboxes with volume_mounts: [{"name": "my-vol", "path": "/mnt/data"}].
- Pause the sandbox:
POST /sandboxes/{id}/pause.
- Query the paused sandbox:
GET /sandboxes/{id}.
- Observed:
volume_mounts is null or missing from the JSON payload.
alias is "" (empty string) if no alias was assigned.
- Expected:
volume_mounts contains [{"name": "my-vol", "path": "/mnt/data"}].
alias is null or omitted when no alias exists.
Technical Context
- File affected:
packages/api/internal/handlers/sandbox_get.go
- Subsystem: Control Plane API / Sandboxes
- Impact: Medium (API Schema compliance, client SDK consistency, and paused sandbox metadata fidelity)
Proposed Changes
| # |
Change |
File(s) Affected |
Complexity |
| 1 |
Add nil check on a.orchestrator != nil before calling GetSandbox |
sandbox_get.go |
Trivial |
| 2 |
Extract volumeMounts from lastSnapshot.Snapshot.Config.VolumeMounts and pass to api.SandboxDetail |
sandbox_get.go |
Low |
| 3 |
Populate sandbox.Alias only when len(lastSnapshot.Aliases) > 0, leaving it nil otherwise |
sandbox_get.go |
Trivial |
| 4 |
Add unit test asserting VolumeMounts and nil alias on paused snapshot |
sandbox_get_test.go |
Low |
Problem
When querying sandbox metadata via
GET /sandboxes/{sandboxID}(packages/api/internal/handlers/sandbox_get.go), paused or stopped sandboxes loaded fromlastSnapshotexhibit two payload inconsistencies with the OpenAPI specification and list endpoints:Missing
VolumeMountsin Paused Sandbox Response:When a sandbox is paused,
lastSnapshot.Snapshot.Config.VolumeMountscontains all persistent volume mount mappings. However,GetSandboxesSandboxIDconstructsapi.SandboxDetailwithout populating theVolumeMountsfield, resulting in"volume_mounts": nullor omission in the JSON response. In contrast, the list endpointGET /v2/sandboxes(packages/api/internal/handlers/sandboxes_list.go:L292) correctly mapsVolumeMountsfor paused sandboxes viaconvertFromDBMountsToAPIMounts.Malformed
AliasSerialized as""(Empty String) instead ofnull/ Omission:When a paused sandbox has no aliases,
pausedAlias := firstAlias(lastSnapshot.Aliases)returns""(empty string).sandbox.Alias = &pausedAliasthen assigns a non-nil pointer to"". When serialized to JSON, this emits"alias": ""instead ofnullor omitting the property, violating the OpenAPI schema contract for optional string fields.Potential Nil Pointer Panic on
a.orchestrator:GetSandboxesSandboxIDattempts to calla.orchestrator.GetSandboxwithout verifyinga.orchestrator != nil, causing panics in isolated unit tests and environments where the orchestrator client is nil.Root Cause
In
packages/api/internal/handlers/sandbox_get.go:L235-L265:Comparison of metadata mapping across endpoints:
VolumeMountsGET /v2/sandboxes(sandboxes_list.go)convertFromDBMountsToAPIMountsnil(omitted)GET /sandboxes/{id}(Current)nil(omitted)&""(serialized as"")GET /sandboxes/{id}(Expected)Config.VolumeMountsnil(omitted)Reproduction Steps
POST /sandboxeswithvolume_mounts: [{"name": "my-vol", "path": "/mnt/data"}].POST /sandboxes/{id}/pause.GET /sandboxes/{id}.volume_mountsisnullor missing from the JSON payload.aliasis""(empty string) if no alias was assigned.volume_mountscontains[{"name": "my-vol", "path": "/mnt/data"}].aliasisnullor omitted when no alias exists.Technical Context
packages/api/internal/handlers/sandbox_get.goProposed Changes
a.orchestrator != nilbefore callingGetSandboxsandbox_get.govolumeMountsfromlastSnapshot.Snapshot.Config.VolumeMountsand pass toapi.SandboxDetailsandbox_get.gosandbox.Aliasonly whenlen(lastSnapshot.Aliases) > 0, leaving itnilotherwisesandbox_get.goVolumeMountsandnilalias on paused snapshotsandbox_get_test.go