From 05be86297184924ba690620f5977e8449a9967ee Mon Sep 17 00:00:00 2001 From: Hector Martinez Date: Wed, 27 May 2026 15:26:01 +0200 Subject: [PATCH] feat(cli): add --keep-sandbox flag to fullsend run Skips sandbox deletion at end of run when --keep-sandbox is set. Prints sandbox name and openshell exec command for direct inspection. Useful for post-failure debugging without re-running the full agent. Closes #1594 Signed-off-by: Hector Martinez --- internal/cli/run.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/cli/run.go b/internal/cli/run.go index 39f76bad4c..2214904b03 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -53,6 +53,7 @@ func newRunCmd() *cobra.Command { var noPostScript bool var debugFilter string var offline bool + var keepSandbox bool cmd := &cobra.Command{ Use: "run ", @@ -62,7 +63,7 @@ func newRunCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { agentName := args[0] printer := ui.New(os.Stdout) - return runAgent(cmd.Context(), agentName, fullsendDir, outputBase, targetRepo, fullsendBinary, envFiles, noPostScript, debugFilter, offline, printer) + return runAgent(cmd.Context(), agentName, fullsendDir, outputBase, targetRepo, fullsendBinary, envFiles, noPostScript, debugFilter, offline, printer, keepSandbox) }, } @@ -72,6 +73,7 @@ func newRunCmd() *cobra.Command { cmd.Flags().StringVar(&fullsendBinary, "fullsend-binary", "", "path to a Linux fullsend binary to copy into the sandbox (default: current executable)") cmd.Flags().StringArrayVar(&envFiles, "env-file", nil, "load environment variables from a dotenv file (repeatable)") cmd.Flags().BoolVar(&noPostScript, "no-post-script", false, "skip post-script execution (agent still runs full inference)") + cmd.Flags().BoolVar(&keepSandbox, "keep-sandbox", false, "skip sandbox deletion after the run (useful for post-failure inspection)") cmd.Flags().StringVar(&debugFilter, "debug", "", `enable Claude Code debug logging with optional category filter (e.g. "api,hooks")`) cmd.Flags().Lookup("debug").NoOptDefVal = "*" cmd.Flags().BoolVar(&offline, "offline", false, "reject network fetches; only use cached remote resources") @@ -81,7 +83,7 @@ func newRunCmd() *cobra.Command { return cmd } -func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRepo, fullsendBinary string, envFiles []string, noPostScript bool, debug string, offline bool, printer *ui.Printer) (runErr error) { +func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRepo, fullsendBinary string, envFiles []string, noPostScript bool, debug string, offline bool, printer *ui.Printer, keepSandbox bool) (runErr error) { printer.Banner(Version()) printer.Blank() printer.Header("Running agent: " + agentName) @@ -366,6 +368,12 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep // Collect OpenShell logs before sandbox deletion for post-mortem debugging. collectOpenshellLogs(sandboxName, runDir, printer) + if keepSandbox { + printer.StepWarn(fmt.Sprintf("Sandbox kept (--keep-sandbox): %s", sandboxName)) + printer.StepInfo(fmt.Sprintf("openshell sandbox exec --name %s -- sh", sandboxName)) + return + } + cleanupStart := time.Now() printer.StepStart("Cleaning up sandbox") if err := sandbox.Delete(sandboxName); err != nil {