Skip to content

Improve local sandbox list reconciliation#394

Merged
robinaugh merged 5 commits intomainfrom
jason/sandbox-list-cli-state
Mar 4, 2026
Merged

Improve local sandbox list reconciliation#394
robinaugh merged 5 commits intomainfrom
jason/sandbox-list-cli-state

Conversation

@robinaugh
Copy link
Contributor

@robinaugh robinaugh commented Mar 3, 2026

Blocked by https://github.com/rwx-cloud/cloud/pull/6975

Summary

  • Sends cli_state (CWD, branch, config file) with every sandbox run so the server can associate runs with their local context.
  • sandbox list uses a bulk API (GET /mint/api/runs?result_status=sandboxed&my_runs=true) instead of N individual connection-info calls, and merges remotely-discovered runs into local storage so sandboxes started on other machines (or otherwise not persisted locally) appear automatically.
  • sandbox exec recovers from the API when no local session exists — reuses a matching remote sandbox instead of creating a new one.
  • sandbox start persists the session immediately after run creation (before scoped token), so a crash mid-setup doesn't orphan the run from local storage.
  • Pruning defaults to expired for runs not in the bulk API response — only keeps them active if the individual fallback positively confirms the run is alive (fixes cancelled runs showing as active).

Test plan

  • sandbox list shows active sandboxes using bulk API
  • sandbox list discovers sandboxes not in local storage via cli_state
  • sandbox list prunes expired/cancelled sandboxes (404, 410, non-404/410 errors)
  • sandbox list keeps initializing sandboxes not yet in bulk API
  • sandbox exec recovers matching sandbox from API when local session is missing
  • sandbox exec falls through to create when no remote match exists
  • sandbox start persists session immediately after run creation
  • Normal sandbox lifecycle (start → exec → list → stop) unaffected
  • Unit tests pass (go test ./internal/... ./cmd/...)
nLPTJnD157tdLzpmO6WirXZZGoDe1LIK0g4QJQmXqzcxPUpip3epsz8GZK_-0EClv9TuPtPd-ucG-20XsGqzS-RSU-wvvRbrpBWYanj0FquuYR1tT0Wznnn2gWtqc4e6-X9wQAtG2iwGwp6QQJXWHqBbpB0CB7ujK77i4emDiIF5K057vT0i8GtQN2iRaIzGwad763q1PmhS26X2Ei55pfI3pRCF7xscJM3MFrS1HvepIPAr
PUML raw
@startuml
    title CLI State for Sandbox Session Recovery
    skinparam sequenceMessageAlign center
    skinparam responseMessageBelowArrow true

    participant "RWX CLI" as CLI
    participant "Local Storage\n(~/.config/rwx/sandboxes.json)" as Storage
    participant "RWX Cloud API" as API

    == Sandbox Start (rwx sandbox start) ==

    CLI -> Storage: Load sessions
    CLI -> CLI: EncodeCliState(cwd, branch, configFile)\n→ base64 JSON
    CLI -> API: POST /mint/api/runs\n{ cli_state: "<base64>" }
    API --> CLI: { run_id, run_url }
    CLI -> Storage: Save session immediately\n(cwd:branch:configFile → runID, no scoped token yet)

    CLI -> API: POST /mint/api/sandbox_tokens\n{ run_id }
    API --> CLI: { token }
    CLI -> Storage: Update session with scoped token

    == Sandbox List (rwx sandbox list) ==

    CLI -> Storage: Load local sessions
    CLI -> API: GET /mint/api/runs\n?result_status=sandboxed&my_runs=true
    API --> CLI: { runs: [{ id, run_url, cli_state }] }

    group Merge remote into local
      CLI -> CLI: For each remote run with cli_state:\nDecodeCliState → (cwd, branch, configFile)
      CLI -> CLI: If no local session exists for key,\ncreate one from remote data
    end

    group Prune expired local sessions
      CLI -> CLI: For each local session:
      alt Run is in bulk API response
        CLI -> CLI: Keep as active
      else Run is NOT in bulk API response
        CLI -> API: GET sandbox connection info\n(individual fallback verification)
        alt 200 OK and not completed
          CLI -> CLI: Keep as active
        else Any error or completed
          CLI -> CLI: Mark as expired (default)
          CLI -> Storage: Remove expired session
        end
      end
    end

    CLI --> CLI: Display merged list

    == Sandbox Exec — Recovery (rwx sandbox exec, no local session) ==

    CLI -> Storage: Load sessions → no match found
    CLI -> API: GET /mint/api/runs\n?result_status=sandboxed&my_runs=true
    API --> CLI: { runs: [{ id, run_url, cli_state }] }
    CLI -> CLI: DecodeCliState for each run\nMatch on cwd + branch + configFile

    alt Match found
      CLI -> API: POST /mint/api/sandbox_tokens\n{ run_id }
      API --> CLI: { token }
      CLI -> Storage: Save recovered session locally
      CLI -> CLI: Connect to sandbox via SSH
    else No match
      CLI -> CLI: Auto-create new sandbox\n(includes cli_state in run creation)
    end

    @enduml

Send a base64-encoded cli_state (cwd, branch, configFile) when creating
sandbox runs so the server can associate runs with their CLI context.
Replace the N+1 GetSandboxConnectionInfo loop in ListSandboxes with a
single ListSandboxRuns bulk API call, and merge remotely-discovered
runs (e.g. from agents) into local storage.
@robinaugh robinaugh self-assigned this Mar 3, 2026
@robinaugh robinaugh changed the title Add cli_state to sandbox runs for cross-device discovery Add cli_state to sandbox runs for better list reconciliation Mar 3, 2026
@robinaugh robinaugh changed the title Add cli_state to sandbox runs for better list reconciliation Improve local sandbox list reconciliation Mar 3, 2026
When ExecSandbox finds no local session, call ListSandboxRuns to check
if a matching sandbox already exists remotely before auto-creating a
new one. This prevents spinning up duplicates when a sandbox is running
but the local storage was lost (e.g. different machine, cleared state).
When a local session's run ID is not in the bulk API response, it may
still be initializing (not yet sandboxed). Fall back to a per-run
GetSandboxConnectionInfo check before pruning to avoid removing
sessions for runs that are still alive.
Save the session to sandboxes.json right after InitiateRun returns,
before scoped token creation. This minimizes the window where a crash
could lose the run ID, ensuring sandbox list can recover the run via
the per-run fallback check even while it's still initializing.
…sResult

The client method ListSandboxRuns() hard-codes query params that scope
it to sandbox runs. Rename the types to match so the client-side intent
is clear.
@robinaugh robinaugh force-pushed the jason/sandbox-list-cli-state branch from be8cdb1 to 94e13c1 Compare March 3, 2026 22:51
@robinaugh robinaugh marked this pull request as ready for review March 3, 2026 23:01
@robinaugh robinaugh merged commit c2f4865 into main Mar 4, 2026
1 check passed
@robinaugh robinaugh deleted the jason/sandbox-list-cli-state branch March 4, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants