Skip to content

feat: run_background_job() on SandboxClient + AsyncSandboxClient#482

Open
hallerite wants to merge 3 commits intomainfrom
run-background-job
Open

feat: run_background_job() on SandboxClient + AsyncSandboxClient#482
hallerite wants to merge 3 commits intomainfrom
run-background-job

Conversation

@hallerite
Copy link
Copy Markdown
Member

@hallerite hallerite commented Mar 28, 2026

Summary

Adds run_background_job() to both SandboxClient and AsyncSandboxClient — a convenience method that combines start_background_job() + polling into a single call.

Problem

Long-running sandbox commands (e.g. running test suites for 10+ minutes) can't use execute_command() because the HTTP connection times out. Users currently have to manually implement start_background_job() + poll get_background_job() in a loop. This pattern is duplicated across multiple TaskSpec implementations and validation code.

Solution

# Before: manual poll loop (repeated everywhere)
job = await client.start_background_job(sandbox_id, command)
while True:
    status = await client.get_background_job(sandbox_id, job)
    if status.completed:
        break
    await asyncio.sleep(3)

# After: one call
result = await client.run_background_job(sandbox_id, command, timeout=900)

API

# Async
result = await client.run_background_job(
    sandbox_id, command,
    timeout=900,            # max seconds to wait
    working_dir="/testbed", # optional
    env={"FOO": "bar"},     # optional
    poll_interval=3,        # seconds between polls
)
# result.completed, result.exit_code, result.stdout, result.stderr

# Sync (same signature, blocking)
result = client.run_background_job(sandbox_id, command, timeout=900)

Raises TimeoutError if the command doesn't complete within the timeout.

🤖 Generated with Claude Code


Note

Low Risk
Low risk additive change that introduces a new helper method; main concern is callers relying on default polling/timeout behavior for long-running commands.

Overview
Adds run_background_job() to both SandboxClient and AsyncSandboxClient, providing a single call that starts a background job, polls get_background_job() until completion, and returns the final BackgroundJobStatus.

The helper supports timeout, working_dir, env, and poll_interval, and raises CommandTimeoutError when the deadline is exceeded.

Written by Cursor Bugbot for commit 4a73c7a. This will update automatically on new commits. Configure here.

Adds run_background_job() to both SandboxClient and AsyncSandboxClient.
Combines start_background_job() + polling into a single convenience
method for long-running commands that would exceed HTTP timeouts with
execute_command().

Usage:
    result = await client.run_background_job(sandbox_id, command, timeout=900)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f75d45244a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1789 to +1793
for _elapsed in range(0, timeout + poll_interval, poll_interval):
status = await self.get_background_job(sandbox_id, job)
if status.completed:
return status
await _asyncio.sleep(poll_interval)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce timeout ceiling in async polling loop

The async implementation can wait significantly longer than the requested timeout because range(0, timeout + poll_interval, poll_interval) schedules an extra poll window and each iteration unconditionally sleeps poll_interval after polling. In practice, timeout=10 and poll_interval=3 can block for about 15 seconds before raising TimeoutError, which contradicts the method contract that timeout is the maximum wait.

Useful? React with 👍 / 👎.

…ration counting

The async version used range() to count loop iterations, which didn't
account for time spent in API calls and could exceed the timeout. Now
uses time.monotonic() deadline matching the sync implementation. Also
removes redundant inline imports since time and asyncio are already
imported at module level.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

…n_background_job

The built-in TimeoutError is uncatchable by users importing TimeoutError
from the package (which is aliased to APITimeoutError). Now raises
CommandTimeoutError, consistent with every other command timeout in
sandbox.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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