From 1f28f4c7ee2c561e2d03865d6f9ee2d857361965 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Wed, 14 Jan 2026 13:25:05 -0800 Subject: [PATCH 1/3] fix for sandbox start_background_job --- packages/prime-sandboxes/src/prime_sandboxes/sandbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py b/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py index 8d21542d..0f19515f 100644 --- a/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py +++ b/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py @@ -441,7 +441,7 @@ def start_background_job( # Start detached process with separate stdout and stderr log files bg_cmd = ( f"nohup sh -c {quoted_sh_command} " - f"> {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" + f"< /dev/null > {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" ) self.execute_command(sandbox_id, bg_cmd, timeout=10) @@ -985,7 +985,7 @@ async def start_background_job( # Start detached process with separate stdout and stderr log files bg_cmd = ( f"nohup sh -c {quoted_sh_command} " - f"> {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" + f"< /dev/null > {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" ) await self.execute_command(sandbox_id, bg_cmd, timeout=10) From 78648692aa825a28875091bfafec2f26e03e77c1 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Wed, 14 Jan 2026 13:39:45 -0800 Subject: [PATCH 2/3] better fix --- .../src/prime_sandboxes/sandbox.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py b/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py index 0f19515f..7fc7a774 100644 --- a/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py +++ b/packages/prime-sandboxes/src/prime_sandboxes/sandbox.py @@ -435,14 +435,14 @@ def start_background_job( stderr_log_file_quoted = shlex.quote(stderr_log_file) # Wrap command in subshell so 'exit' terminates the subshell, not the outer shell. # This ensures 'echo $?' always runs to capture the exit code. - sh_command = f"({command_body}); echo $? > {exit_file_quoted}" + sh_command = ( + f"({command_body}) > {stdout_log_file_quoted} 2> {stderr_log_file_quoted}; " + f"echo $? > {exit_file_quoted}" + ) quoted_sh_command = shlex.quote(sh_command) - # Start detached process with separate stdout and stderr log files - bg_cmd = ( - f"nohup sh -c {quoted_sh_command} " - f"< /dev/null > {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" - ) + # Outer nohup redirects to /dev/null since output goes to log files inside sh -c + bg_cmd = f"nohup sh -c {quoted_sh_command} < /dev/null > /dev/null 2>&1 &" self.execute_command(sandbox_id, bg_cmd, timeout=10) return BackgroundJob( @@ -979,14 +979,14 @@ async def start_background_job( stderr_log_file_quoted = shlex.quote(stderr_log_file) # Wrap command in subshell so 'exit' terminates the subshell, not the outer shell. # This ensures 'echo $?' always runs to capture the exit code. - sh_command = f"({command_body}); echo $? > {exit_file_quoted}" + sh_command = ( + f"({command_body}) > {stdout_log_file_quoted} 2> {stderr_log_file_quoted}; " + f"echo $? > {exit_file_quoted}" + ) quoted_sh_command = shlex.quote(sh_command) - # Start detached process with separate stdout and stderr log files - bg_cmd = ( - f"nohup sh -c {quoted_sh_command} " - f"< /dev/null > {stdout_log_file_quoted} 2> {stderr_log_file_quoted} &" - ) + # Outer nohup redirects to /dev/null since output goes to log files inside sh -c + bg_cmd = f"nohup sh -c {quoted_sh_command} < /dev/null > /dev/null 2>&1 &" await self.execute_command(sandbox_id, bg_cmd, timeout=10) return BackgroundJob( From 4b7733709bc9efc272117e853fea6d369d3733cc Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Thu, 15 Jan 2026 11:38:21 -0800 Subject: [PATCH 3/3] add test --- .../tests/test_command_execution.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/prime-sandboxes/tests/test_command_execution.py b/packages/prime-sandboxes/tests/test_command_execution.py index b57aa959..aeb3424c 100644 --- a/packages/prime-sandboxes/tests/test_command_execution.py +++ b/packages/prime-sandboxes/tests/test_command_execution.py @@ -226,6 +226,28 @@ def test_start_background_job(sandbox_client, shared_sandbox): print(f"✓ Background execution completed: {status.stdout.strip()}") +def test_start_background_job_returns_immediately(sandbox_client, shared_sandbox): + """Test that start_background_job returns immediately without waiting for the job.""" + print("\nTesting start_background_job returns immediately...") + + start_time = time.time() + job = sandbox_client.start_background_job( + shared_sandbox.id, + "sleep 30 && echo done", + ) + elapsed = time.time() - start_time + + assert job.job_id is not None + # Should return in under 5 seconds, not 30 + assert elapsed < 5, f"start_background_job took {elapsed:.1f}s, expected < 5s" + print(f"✓ Job started in {elapsed:.2f}s (sleep 30 is running in background)") + + # Verify job is still running (not completed yet) + status = sandbox_client.get_background_job(shared_sandbox.id, job) + assert not status.completed, "Job should still be running" + print("✓ Job correctly running in background") + + def test_start_background_job_with_working_dir(sandbox_client, shared_sandbox): """Test start_background_job with working directory""" sandbox_client.execute_command(shared_sandbox.id, "mkdir -p /tmp/bgtest")