Skip to content

Commit 47b4024

Browse files
authored
Add --reset flag to sandbox exec and reconnection hint on 2nd exec (#484)
1 parent 6185c48 commit 47b4024

File tree

7 files changed

+524
-9
lines changed

7 files changed

+524
-9
lines changed

cmd/rwx/sandbox.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ CONFIG FILE
207207
Json: useJson,
208208
Sync: !sandboxNoSync,
209209
InitParameters: initParams,
210+
Reset: sandboxReset,
210211
})
211212
if err != nil {
212213
return err
@@ -388,6 +389,7 @@ var (
388389
sandboxOpen bool
389390
sandboxWait bool
390391
sandboxNoSync bool
392+
sandboxReset bool
391393
sandboxInitParams []string
392394
)
393395

@@ -411,6 +413,7 @@ func init() {
411413
sandboxExecCmd.Flags().StringVar(&sandboxRunID, "id", "", "Use specific run ID")
412414
sandboxExecCmd.Flags().BoolVar(&sandboxOpen, "open", false, "Open the run in a browser")
413415
sandboxExecCmd.Flags().BoolVar(&sandboxNoSync, "no-sync", false, "Skip syncing local changes before execution")
416+
sandboxExecCmd.Flags().BoolVar(&sandboxReset, "reset", false, "Reset the sandbox before executing")
414417
sandboxExecCmd.Flags().StringArrayVar(&sandboxInitParams, "init", []string{}, "initialization parameters for the sandbox run, available in the `init` context. Can be specified multiple times")
415418

416419
// stop flags

internal/cli/sandbox_storage.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ func DecodeCliState(encoded string) (*CliState, error) {
4040
}
4141

4242
type SandboxSession struct {
43-
RunID string `json:"runId"`
44-
ConfigFile string `json:"configFile"`
45-
ScopedToken string `json:"scopedToken,omitempty"`
46-
RunURL string `json:"runUrl,omitempty"`
47-
ConfigHash string `json:"configHash,omitempty"`
48-
CreatedAt *time.Time `json:"createdAt,omitempty"`
49-
LastExecAt *time.Time `json:"lastExecAt,omitempty"`
50-
ExecCount int `json:"execCount,omitempty"`
43+
RunID string `json:"runId"`
44+
ConfigFile string `json:"configFile"`
45+
ScopedToken string `json:"scopedToken,omitempty"`
46+
RunURL string `json:"runUrl,omitempty"`
47+
ConfigHash string `json:"configHash,omitempty"`
48+
CreatedAt *time.Time `json:"createdAt,omitempty"`
49+
LastExecAt *time.Time `json:"lastExecAt,omitempty"`
50+
ExecCount int `json:"execCount,omitempty"`
51+
ResetNagShown bool `json:"resetNagShown,omitempty"`
5152
}
5253

5354
// HashConfigFile returns a hex-encoded SHA-256 hash of the file at the given path.

internal/cli/service_sandbox.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type ExecSandboxConfig struct {
5252
Json bool
5353
Sync bool
5454
InitParameters map[string]string
55+
Reset bool
5556
}
5657

5758
type ListSandboxesConfig struct {
@@ -434,6 +435,9 @@ func (s Service) ExecSandbox(cfg ExecSandboxConfig) (*ExecSandboxResult, error)
434435
var scopedToken string
435436
var sessionRunURL string
436437
var storedConfigHash string
438+
var execCount int
439+
var resetNagShown bool
440+
isNewSandbox := false
437441

438442
// Sandbox selection priority:
439443
// 1. --id flag
@@ -501,6 +505,8 @@ func (s Service) ExecSandbox(cfg ExecSandboxConfig) (*ExecSandboxResult, error)
501505
scopedToken = session.ScopedToken
502506
sessionRunURL = session.RunURL
503507
storedConfigHash = session.ConfigHash
508+
execCount = session.ExecCount
509+
resetNagShown = session.ResetNagShown
504510
}
505511
}
506512
} else {
@@ -533,6 +539,8 @@ func (s Service) ExecSandbox(cfg ExecSandboxConfig) (*ExecSandboxResult, error)
533539
scopedToken = activeSessions[0].ScopedToken
534540
sessionRunURL = activeSessions[0].RunURL
535541
storedConfigHash = activeSessions[0].ConfigHash
542+
execCount = activeSessions[0].ExecCount
543+
resetNagShown = activeSessions[0].ResetNagShown
536544
found = true
537545
} else if len(activeSessions) > 1 {
538546
UnlockSandboxStorage(lockFile)
@@ -606,10 +614,40 @@ func (s Service) ExecSandbox(cfg ExecSandboxConfig) (*ExecSandboxResult, error)
606614
}
607615
}
608616

617+
if found && cfg.Reset {
618+
connInfo, connErr := s.APIClient.GetSandboxConnectionInfo(runID, scopedToken)
619+
if connErr == nil && connInfo.Sandboxable {
620+
if sshErr := s.connectSSH(&connInfo); sshErr == nil {
621+
_, _ = s.SSHClient.ExecuteCommand("__rwx_sandbox_end__")
622+
s.SSHClient.Close()
623+
} else {
624+
if cancelErr := s.APIClient.CancelRun(runID, scopedToken); cancelErr != nil {
625+
fmt.Fprintf(s.Stderr, "Warning: failed to cancel run %s: %v\n", runID, cancelErr)
626+
}
627+
}
628+
} else if connErr == nil && !connInfo.Polling.Completed {
629+
if cancelErr := s.APIClient.CancelRun(runID, scopedToken); cancelErr != nil {
630+
fmt.Fprintf(s.Stderr, "Warning: failed to cancel run %s: %v\n", runID, cancelErr)
631+
}
632+
}
633+
s.waitForSandboxCompletion(runID, scopedToken)
634+
storage.DeleteSession(branch, configFile)
635+
if saveErr := storage.Save(); saveErr != nil {
636+
fmt.Fprintf(s.Stderr, "Warning: unable to save sandbox sessions: %v\n", saveErr)
637+
}
638+
found = false
639+
runID = ""
640+
configFile = ""
641+
scopedToken = ""
642+
sessionRunURL = ""
643+
storedConfigHash = ""
644+
}
645+
609646
if !found {
610647
// Pass the lock to StartSandbox so the "no session found → create
611648
// new sandbox → persist session" sequence is atomic. StartSandbox
612649
// will release it after the initial session is saved.
650+
isNewSandbox = true
613651
startResult, err := s.StartSandbox(StartSandboxConfig{
614652
ConfigFile: cfgFile,
615653
RwxDirectory: cfg.RwxDirectory,
@@ -637,6 +675,20 @@ func (s Service) ExecSandbox(cfg ExecSandboxConfig) (*ExecSandboxResult, error)
637675
}
638676
}
639677

678+
if !isNewSandbox && execCount >= 1 && !resetNagShown {
679+
fmt.Fprintf(s.Stderr, "Reconnecting to existing sandbox. To re-run setup tasks, use: rwx sandbox exec --reset -- <command>\n")
680+
if nagLock, nagLockErr := s.lockSandboxStorageWithInfo(cfg.Json); nagLockErr == nil {
681+
if nagStorage, nagLoadErr := LoadSandboxStorage(); nagLoadErr == nil {
682+
if nagSession, ok := nagStorage.GetSession(branch, configFile); ok {
683+
nagSession.ResetNagShown = true
684+
nagStorage.SetSession(branch, configFile, *nagSession)
685+
_ = nagStorage.Save()
686+
}
687+
}
688+
UnlockSandboxStorage(nagLock)
689+
}
690+
}
691+
640692
// Warn if the sandbox definition has changed since this sandbox was started
641693
if storedConfigHash != "" && configFile != "" {
642694
currentHash := HashConfigFile(configFile)

0 commit comments

Comments
 (0)