Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions packages/prime-sandboxes/src/prime_sandboxes/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"> {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(
Expand Down Expand Up @@ -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"> {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(
Expand Down
22 changes: 22 additions & 0 deletions packages/prime-sandboxes/tests/test_command_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down