Skip to content

fix(api): GetSandboxesSandboxID omits VolumeMounts and sets empty string Alias for paused sandboxes #3575

Description

@chill-czar

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:

  1. 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.

  2. 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.

  3. 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

  1. Create a sandbox with persistent volume mounts: POST /sandboxes with volume_mounts: [{"name": "my-vol", "path": "/mnt/data"}].
  2. Pause the sandbox: POST /sandboxes/{id}/pause.
  3. Query the paused sandbox: GET /sandboxes/{id}.
  4. Observed:
    • volume_mounts is null or missing from the JSON payload.
    • alias is "" (empty string) if no alias was assigned.
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions