Skip to content

Add Docker support for Modal sandboxes#697

Open
ColeMurray wants to merge 12 commits into
mainfrom
feat/modal-docker-compose-support
Open

Add Docker support for Modal sandboxes#697
ColeMurray wants to merge 12 commits into
mainfrom
feat/modal-docker-compose-support

Conversation

@ColeMurray
Copy link
Copy Markdown
Owner

@ColeMurray ColeMurray commented Jun 1, 2026

Summary

  • add Docker/Docker Compose runtime support for Modal sandboxes, including runtime services, env plumbing, and Terraform/configuration docs
  • add Docker-aware repo image profiles so prebuilt images and snapshot restores use the matching base image
  • expose Docker sandbox settings in the web UI without backend feature gating
  • persist snapshot image profile provenance and ignore snapshots whose profile does not match current sandbox settings

Tests

  • pnpm exec tsc -p packages/shared/tsconfig.json --noEmit
  • pnpm exec tsc -p packages/control-plane/tsconfig.json --noEmit
  • pnpm exec eslint packages/shared/src/types/integrations.ts packages/shared/src/types/integrations.test.ts packages/control-plane/src/sandbox/provider.ts packages/control-plane/src/sandbox/client.ts packages/control-plane/src/sandbox/providers/daytona-provider.ts packages/control-plane/src/sandbox/lifecycle/manager.ts packages/control-plane/src/sandbox/lifecycle/manager.test.ts packages/control-plane/src/session/types.ts packages/control-plane/src/session/repository.ts packages/control-plane/src/session/repository.test.ts packages/control-plane/src/session/schema.ts packages/control-plane/src/session/durable-object.ts packages/control-plane/src/routes/repo-images.ts packages/control-plane/vitest.config.ts packages/control-plane/vitest.integration.config.ts packages/control-plane/src/session/http/handlers/session-lifecycle.handler.test.ts packages/control-plane/src/session/http/handlers/child-sessions.handler.test.ts packages/control-plane/src/session/websocket-manager.test.ts
  • pnpm exec vitest run packages/control-plane/src/db/integration-settings.test.ts packages/control-plane/src/db/repo-images.test.ts packages/control-plane/src/sandbox/lifecycle/manager.test.ts packages/shared/src/types/integrations.test.ts
  • pnpm exec vitest run packages/shared/src/types/integrations.test.ts packages/control-plane/src/session/repository.test.ts packages/control-plane/src/sandbox/lifecycle/manager.test.ts packages/control-plane/src/sandbox/client.test.ts
  • ../../node_modules/.bin/vitest run --config vitest.integration.config.ts test/integration/repo-images.test.ts
  • PYTHONPATH=src python -m pytest tests/test_docker_service.py
  • python -m pytest tests/test_scheduler.py tests/test_sandbox_env_vars.py tests/test_build_sandbox.py
  • python -m pytest tests/test_image_builder_v2.py tests/test_ttyd.py tests/test_tunnel_ports.py tests/test_web_api_create_sandbox.py
  • npm --workspace @open-inspect/control-plane run build
  • ruff check src tests
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Optional Docker Engine/Compose support for sandboxes with configurable CPU/memory and image-profile-aware builds/snapshots; UI controls for per-repo/global Docker behavior and port/terminal handling.
  • Documentation

    • Guides and deployment docs updated with Docker examples, startup ordering, resource defaults, prebuild tips, and port/tunnel guidance.
  • Deployment / Config

    • CI and Terraform now expose optional Docker sandbox sizing variables and secrets.
  • Tests

    • Expanded test coverage for Docker profiles, scheduler, lifecycle, and sandbox runtime behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional Docker Engine support for Modal-backed sandboxes: typed runtime settings and image profiles, Modal launch orchestration, Docker daemon supervisor and runtime integration, DB schema/profile scoping, control-plane batching and lifecycle compatibility, scheduler/profile-aware builds, web UI controls, docs, and tests.

Changes

Docker Engine Support for Modal Sandboxes

Layer / File(s) Summary
Docs, CI, Terraform
.github/workflows/terraform.yml, docs/*, README.md, terraform/*, packages/modal-infra/.env.example, packages/modal-infra/README.md, packages/modal-infra/src/app.py
Adds Docker docs, Terraform variables/secrets, CI workflow env vars, and deployment examples for Docker-enabled sandboxes.
Shared types & normalization
packages/shared/src/types/integrations.ts, packages/shared/src/types/integrations.test.ts
Adds SandboxImageProfile, SandboxRuntimeSettings, normalization and profile resolution helpers and tests.
Modal infra images
packages/modal-infra/src/images/base.py, packages/modal-infra/src/images/__init__.py
Adds docker_image variant with pinned tooling and exposes it from images package.
Modal infra: runtime settings & launch options
packages/modal-infra/src/sandbox/settings.py, packages/modal-infra/src/sandbox/launch_options.py, packages/modal-infra/src/sandbox/manager.py
Adds typed runtime settings, Docker launch settings, RuntimeLaunchOptions and refactors sandbox creation/restore/build to use centralized launch options and image_profile.
Sandbox runtime: DockerService & RuntimeServices
packages/sandbox-runtime/src/sandbox_runtime/docker_service.py, packages/sandbox-runtime/src/sandbox_runtime/runtime_services.py, packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
Implements DockerService to manage dockerd, integrates runtime services into supervisor lifecycle, adds start-before-hooks, health checks, and graceful stop.
DB and repo image profiles
terraform/d1/migrations/0022_add_repo_image_profile.sql, packages/control-plane/src/db/repo-images.ts, packages/control-plane/src/db/repo-images.test.ts, packages/control-plane/src/session/schema.ts, packages/control-plane/src/session/repository.ts, packages/control-plane/src/session/types.ts
Adds image_profile to repo_images and snapshot_image_profile to sandbox rows; persists and scopes repo-image queries by profile.
Control plane settings & batching
packages/control-plane/src/db/integration-settings.ts, packages/control-plane/src/db/integration-settings.test.ts, packages/control-plane/src/session/integration-settings-resolution.ts
Adds getRepoSettingsBatch/getResolvedConfigs and resolveSandboxSettingsForRepos; validates dockerEnabled input.
Lifecycle manager & snapshot compatibility
packages/control-plane/src/sandbox/lifecycle/manager.ts, tests
Derives runtime settings/imageProfile from sessions, gates snapshot restore on profile equality, persists snapshot profile, and updates spawn flows.
Routes, client, provider, build planner
packages/control-plane/src/routes/repo-images.ts, packages/control-plane/src/sandbox/client.ts, packages/control-plane/src/sandbox/provider.ts, packages/control-plane/src/repo-images/build-planner.ts, packages/control-plane/* tests
Adds RepoImageBuildPlanner, threads sandboxSettings and image_profile into DB/register and Modal buildRepoImage requests, updates provider/client typings.
Scheduler & image builder
packages/modal-infra/src/scheduler/image_builder.py, tests
Makes build triggering and rebuild logic image-profile-aware; validates/forwards image_profile and sandbox_settings into build sandboxes.
Web UI: settings model and editor
packages/web/src/components/settings/sandbox-settings-model.ts, packages/web/src/components/settings/sandbox-settings.tsx, tests
Adds DockerMode, port helpers, buildSettingsPayload, integrates Docker UI controls and change detection.
Tests & tooling
packages/*/tests/*, packages/control-plane/vitest*.ts, packages/control-plane/tsconfig.json, packages/control-plane/package.json
Updates and adds unit and integration tests across control-plane, modal-infra, sandbox-runtime, shared types, and web UI; adds path aliases for shared package in build/test configs.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ControlPlane
  participant Scheduler
  participant ModalInfra
  participant SandboxRuntime
  Client->>ControlPlane: POST /repo-images/trigger (owner/name)
  ControlPlane->>ControlPlane: RepoImageBuildPlanner.plan(repo) => sandboxSettings,imageProfile
  ControlPlane->>ControlPlane.DB: INSERT repo_images (image_profile)
  ControlPlane->>ModalInfra: buildRepoImage(sandboxSettings, image_profile)
  ModalInfra->>Scheduler: spawn build (sandbox_settings, image_profile)
  Scheduler->>ModalInfra: create_build_sandbox(image_profile, runtime_settings)
  ModalInfra->>SandboxRuntime: start sandbox (runtime_settings, image_profile)
  SandboxRuntime->>SandboxRuntime: RuntimeServices.start_before_hooks()
  SandboxRuntime->>SandboxRuntime: DockerService.start() if image_profile == "docker"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"I'm a rabbit in a sandbox den,
I nudge the daemon, kick up sand again.
Profiles sorted, ports set right,
Snapshots safe and builds take flight.
🐇🚀"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/modal-docker-compose-support

Comment thread packages/modal-infra/src/sandbox/settings.py


@dataclass(frozen=True)
class RuntimeLaunchOptions:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[deep review] This is the right concept, but it is landing inside an already broad 870-line SandboxManager, so image selection, Docker Modal kwargs, env injection, and port planning now all live in the lifecycle class. Can we move RuntimeLaunchOptions into sandbox/settings.py (or a small launch_options.py) and make SandboxManager just call build_modal_create_kwargs(...)? That keeps the manager as orchestration and stops it becoming the kitchen-sink place for every new runtime feature.

try {
const repos = await metadataStore.getImageBuildEnabledRepos();
return json({ repos });
const sandboxSettings = (await resolveSandboxSettingsForRepos(env.DB, repos)).map((settings) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[deep review] This adds another copy of “resolve sandbox settings -> normalize -> derive image profile” in the route layer, while trigger-build above repeats the same derivation again. The repo-image subsystem now has an implicit build identity spread across route handlers and the scheduler response shape. Can we push this behind a repo-image build planner/store method that returns { repo, sandboxSettings, imageProfile } and use that both for scheduler listing and trigger registration? The current structure makes it too easy for a future profile-affecting setting to update one path and miss the other.

? (data as GlobalSettingsResponse | undefined)?.settings?.defaults
: globalData?.settings?.defaults;
const repoSettings = isGlobal ? undefined : (data as RepoSettingsResponse | undefined)?.settings;
const canEditDockerSettings = getPublicSandboxProvider() === "modal";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[deep review] This makes Docker editability depend on a client-side public provider flag, but the integration setting itself is provider-agnostic and the backend accepts/persists dockerEnabled regardless. That split is a leaky boundary: the UI can hide an existing repo/global Docker setting while buildSettingsPayload still preserves it from baseSettings, and the control plane/Modal path remains the actual authority. Can we remove the client provider gate and let the backend/sandbox provider decide whether the setting is actionable, or return capability metadata from the settings API? Otherwise this becomes scattered feature gating.

Copy link
Copy Markdown
Contributor

@open-inspect open-inspect Bot left a comment

Choose a reason for hiding this comment

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

Summary

PR #697, "Add Docker support for Modal sandboxes" by @ColeMurray, changes 88 files with +4928/-2417. I reviewed the Docker runtime/service lifecycle, Modal launch settings, control-plane settings/image-profile plumbing, snapshot profile handling, repo-image scheduling, settings UI, and the session route split.

Critical Issues

None found.

Suggestions

None blocking. The Docker image-profile separation, normalized runtime settings boundary, and snapshot profile compatibility checks address the main correctness risks I would expect from this change.

Nitpicks

None.

Positive Feedback

The profile-aware repo image lookup and snapshot provenance handling are a good guard against restoring or reusing images with incompatible Docker support.

The runtime settings normalization is consistently applied before crossing into providers, which keeps stringly/untrusted settings from enabling privileged runtime features.

The tests added around repo-image profiles, sandbox lifecycle settings, and UI payload preservation cover the important regression paths.

Questions

None.

Verdict

Approve. I did not identify blocking correctness, security, or maintainability issues in the diff.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
packages/control-plane/src/routes/session-prompt.ts (1)

77-86: 💤 Low value

Rename shadowing error callback param.

The catch parameter error shadows the error helper imported at Line 8. It's harmless here but may trip ESLint no-shadow and hurts readability. Consider err.

♻️ Suggested rename
-    store.touchUpdatedAt(sessionId).catch((error) => {
+    store.touchUpdatedAt(sessionId).catch((err) => {
       logger.error("session_index.touch_updated_at.background_error", {
         session_id: sessionId,
         trace_id: ctx.trace_id,
         request_id: ctx.request_id,
-        error,
+        error: err,
       });
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/routes/session-prompt.ts` around lines 77 - 86,
The catch callback passed to ctx.executionCtx?.waitUntil uses the parameter name
"error" which shadows the imported helper "error"; change the catch parameter to
a non-shadowing name (e.g., "err") and update its use inside the logger.error
call so the thrown error is logged via the new name; locate the block around
ctx.executionCtx?.waitUntil(...) that calls
store.touchUpdatedAt(sessionId).catch(...) and replace the parameter and
references consistently (e.g., err) to avoid shadowing.
docs/IMAGE_PREBUILD.md (1)

115-128: 💤 Low value

Consider adding rationale for stopping containers.

The documentation states "stop them before the script exits" but doesn't explain why this is necessary. Adding a brief explanation (e.g., "to avoid capturing stale running container state in the snapshot") would help users understand the importance of this step.

📝 Suggested enhancement
 For Docker Compose projects, keep long-running services in `.openinspect/start.sh` and use
 `.openinspect/setup.sh` for install, build, pull, and cache-warming work. If setup starts containers
-as part of a build, stop them before the script exits, for example with `docker compose down`, so
-the snapshot contains reusable image and volume data without stale running container state.
+as part of a build, stop them before the script exits (e.g., with `docker compose down`). This ensures
+the snapshot contains reusable image and volume data without stale running container state, which would
+prevent the containers from starting cleanly when the snapshot is restored.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/IMAGE_PREBUILD.md` around lines 115 - 128, The doc asks users to "stop
them before the script exits" but omits the rationale; update the Docker-Enabled
Images section to add a short explanation after the instruction to stop
containers (referencing .openinspect/setup.sh and .openinspect/start.sh)
clarifying that stopping containers ensures snapshots do not capture stale
running container state or ephemeral processes and that volumes and images are
left in a reusable state for subsequent runs.
packages/sandbox-runtime/src/sandbox_runtime/docker_service.py (2)

138-160: ⚡ Quick win

Clean up dockerd and the log task when readiness fails.

If wait_until_ready() raises (early exit or timeout), the spawned dockerd process is never terminated and leaks as an orphaned daemon — a subsequent retry can then collide on /var/run/docker.sock. Additionally, the _forward_logs() task is created without retaining a reference; per the asyncio docs the event loop only keeps a weak reference, so the task may be garbage-collected mid-execution.

♻️ Proposed fix
         self.process = await asyncio.create_subprocess_exec(
             "dockerd",
             f"--data-root={self.data_root}",
             "--host=unix:///var/run/docker.sock",
             "--iptables=false",
             "--ip6tables=false",
             stdout=asyncio.subprocess.PIPE,
             stderr=asyncio.subprocess.STDOUT,
         )
-        asyncio.create_task(self._forward_logs())
-        await self.wait_until_ready()
-        self.log.info("docker.ready", data_root=str(self.data_root))
+        self._log_task = asyncio.create_task(self._forward_logs())
+        try:
+            await self.wait_until_ready()
+        except Exception:
+            await self.stop()
+            raise
+        self.log.info("docker.ready", data_root=str(self.data_root))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sandbox-runtime/src/sandbox_runtime/docker_service.py` around lines
138 - 160, The current startup creates self.process and fires off
asyncio.create_task(self._forward_logs()) without keeping a reference and never
shuts down the process or task if wait_until_ready() fails, causing orphaned
dockerd and potential sock collisions; fix by storing the log task (e.g.,
self._log_task = asyncio.create_task(self._forward_logs())), and ensure any
cleanup path (in wait_until_ready() on early exit or timeout and in the caller
that launches dockerd) terminates the daemon and cancels the log task: if
self.process exists and readiness fails, call self.process.terminate()/kill()
and await self.process.wait(), cancel and await self._log_task (handling
asyncio.CancelledError), then re-raise the original error; update references in
wait_until_ready, the code that creates the process, and any teardown to use
self._log_task for cancellation.

176-180: ⚡ Quick win

Extract the shutdown timeout into a named constant.

5.0 is a bare timeout literal. Define it once as a named, unit-encoded constant (e.g. STOP_TIMEOUT_SECONDS = 5.0) alongside READY_TIMEOUT_SECONDS.

♻️ Proposed fix
     STALE_DATA_DIRS = ("network", "containers", "containerd", "runtimes", "tmp")
     READY_TIMEOUT_SECONDS = 30.0
+    STOP_TIMEOUT_SECONDS = 5.0
-            await asyncio.wait_for(self.process.wait(), timeout=5.0)
+            await asyncio.wait_for(self.process.wait(), timeout=self.STOP_TIMEOUT_SECONDS)

As per coding guidelines: "Define each default timeout value exactly once as a named constant and import it everywhere rather than restating literal values."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sandbox-runtime/src/sandbox_runtime/docker_service.py` around lines
176 - 180, Replace the bare timeout literal 5.0 used in the asyncio.wait_for
call with a named, unit-encoded module-level constant (e.g. STOP_TIMEOUT_SECONDS
= 5.0) defined alongside READY_TIMEOUT_SECONDS; update the wait logic in the
method that calls asyncio.wait_for(self.process.wait(), ...) to use
STOP_TIMEOUT_SECONDS, and ensure any other occurrences of the same shutdown
timeout use that constant (and are imported) instead of repeating the literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/control-plane/src/routes/session-child-spawn.ts`:
- Around line 44-48: The code calls resolveSandboxSettings and accesses
parentSession properties without verifying parentSession exists; update the
logic around sessionStore.get(parentId) to check for a missing parentSession and
return an explicit error (or throw) early instead of proceeding: if
parentSession is null/undefined, do not call resolveSandboxSettings, set
parentUserId to null and respond with a clear error message referencing parentId
(or throw), otherwise keep the existing behavior of computing parentUserId from
parentSession.userId and calling resolveSandboxSettings(parentSession.repoOwner,
parentSession.repoName).

In `@packages/control-plane/src/routes/session-prompt.ts`:
- Around line 22-34: The handler currently calls await request.json() directly
which throws on empty or non-JSON bodies; wrap the JSON parse in a try/catch
around the request.json() call in session-prompt.ts (the code that assigns to
the body variable) and return error("invalid JSON body") (or similar 400
response) when parsing fails or the body is missing, then proceed with the
existing check for body.content (the error("content is required") branch);
ensure you catch and handle both malformed JSON and empty bodies before any
further processing.

In `@packages/control-plane/src/routes/session-ws-token.ts`:
- Around line 20-33: The handler currently calls await request.json() unguarded
so malformed JSON throws and becomes a 500; wrap the request.json() call in a
try/catch around the assignment to body (the body variable populated from
request.json()), and on a parse error return a client error (use error("Invalid
JSON", 400) or similar) instead of letting the exception propagate; keep the
existing shape check for body.userId afterwards and only proceed when parsing
succeeds.

In `@packages/modal-infra/src/images/base.py`:
- Around line 248-259: The runc binary is downloaded without integrity checks;
update the image build where .run_commands constructs the runc installation
(reference RUNC_VERSION and the runc.amd64 filename) to download to a temporary
file, compute and verify a SHA-256 against a new RUNC_SHA256 constant, and
abort/log error if the checksum does not match before making it executable and
moving it to /usr/local/bin/runc; ensure the steps replace the direct
wget->chmod->mv sequence with download->sha256sum verification->chmod->mv so the
build fails on mismatch.

In `@packages/modal-infra/src/sandbox/manager.py`:
- Around line 133-143: select_runtime_image currently loads snapshots with
modal.Image.from_registry but restore_from_snapshot uses modal.Image.from_id and
snapshot_filesystem() returns an object_id; change select_runtime_image to load
snapshots using modal.Image.from_id(snapshot_id) instead of from_registry so
snapshot_id is treated as a Modal image object ID and behavior matches
restore_from_snapshot and snapshot_filesystem; update any docstring or callers
if snapshot_id semantics differ.

In `@packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py`:
- Line 1547: Wrap the call to self.runtime_services.stop() with an
asyncio.wait_for to enforce a timeout (use the existing SIDECAR_TIMEOUT_SECONDS
constant for consistency), e.g. await
asyncio.wait_for(self.runtime_services.stop(), SIDECAR_TIMEOUT_SECONDS), and
catch asyncio.TimeoutError to log a warning/error and continue shutdown; ensure
asyncio is imported and use the same logger used elsewhere in this method (e.g.
self.process_logger or process_logger) to record the timeout event.

---

Nitpick comments:
In `@docs/IMAGE_PREBUILD.md`:
- Around line 115-128: The doc asks users to "stop them before the script exits"
but omits the rationale; update the Docker-Enabled Images section to add a short
explanation after the instruction to stop containers (referencing
.openinspect/setup.sh and .openinspect/start.sh) clarifying that stopping
containers ensures snapshots do not capture stale running container state or
ephemeral processes and that volumes and images are left in a reusable state for
subsequent runs.

In `@packages/control-plane/src/routes/session-prompt.ts`:
- Around line 77-86: The catch callback passed to ctx.executionCtx?.waitUntil
uses the parameter name "error" which shadows the imported helper "error";
change the catch parameter to a non-shadowing name (e.g., "err") and update its
use inside the logger.error call so the thrown error is logged via the new name;
locate the block around ctx.executionCtx?.waitUntil(...) that calls
store.touchUpdatedAt(sessionId).catch(...) and replace the parameter and
references consistently (e.g., err) to avoid shadowing.

In `@packages/sandbox-runtime/src/sandbox_runtime/docker_service.py`:
- Around line 138-160: The current startup creates self.process and fires off
asyncio.create_task(self._forward_logs()) without keeping a reference and never
shuts down the process or task if wait_until_ready() fails, causing orphaned
dockerd and potential sock collisions; fix by storing the log task (e.g.,
self._log_task = asyncio.create_task(self._forward_logs())), and ensure any
cleanup path (in wait_until_ready() on early exit or timeout and in the caller
that launches dockerd) terminates the daemon and cancels the log task: if
self.process exists and readiness fails, call self.process.terminate()/kill()
and await self.process.wait(), cancel and await self._log_task (handling
asyncio.CancelledError), then re-raise the original error; update references in
wait_until_ready, the code that creates the process, and any teardown to use
self._log_task for cancellation.
- Around line 176-180: Replace the bare timeout literal 5.0 used in the
asyncio.wait_for call with a named, unit-encoded module-level constant (e.g.
STOP_TIMEOUT_SECONDS = 5.0) defined alongside READY_TIMEOUT_SECONDS; update the
wait logic in the method that calls asyncio.wait_for(self.process.wait(), ...)
to use STOP_TIMEOUT_SECONDS, and ensure any other occurrences of the same
shutdown timeout use that constant (and are imported) instead of repeating the
literal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0b69b91-106f-465e-9c24-ca1e203ecf93

📥 Commits

Reviewing files that changed from the base of the PR and between b762733 and 2cb61ec.

📒 Files selected for processing (88)
  • .github/workflows/terraform.yml
  • README.md
  • docs/GETTING_STARTED.md
  • docs/HOW_IT_WORKS.md
  • docs/IMAGE_PREBUILD.md
  • packages/control-plane/package.json
  • packages/control-plane/src/db/integration-settings.test.ts
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/db/repo-images.test.ts
  • packages/control-plane/src/db/repo-images.ts
  • packages/control-plane/src/router.create-session.test.ts
  • packages/control-plane/src/router.provider-identities.test.ts
  • packages/control-plane/src/router.ts
  • packages/control-plane/src/routes/provider-identities.test.ts
  • packages/control-plane/src/routes/provider-identities.ts
  • packages/control-plane/src/routes/repo-images.ts
  • packages/control-plane/src/routes/session-child-spawn.ts
  • packages/control-plane/src/routes/session-children.ts
  • packages/control-plane/src/routes/session-create.ts
  • packages/control-plane/src/routes/session-index.test.ts
  • packages/control-plane/src/routes/session-index.ts
  • packages/control-plane/src/routes/session-media-artifacts.ts
  • packages/control-plane/src/routes/session-media-stream.ts
  • packages/control-plane/src/routes/session-media-upload.ts
  • packages/control-plane/src/routes/session-media.ts
  • packages/control-plane/src/routes/session-prompt.ts
  • packages/control-plane/src/routes/session-route.ts
  • packages/control-plane/src/routes/session-runtime-proxy.test.ts
  • packages/control-plane/src/routes/session-runtime-proxy.ts
  • packages/control-plane/src/routes/session-ws-token.ts
  • packages/control-plane/src/routes/sessions.ts
  • packages/control-plane/src/sandbox/client.test.ts
  • packages/control-plane/src/sandbox/client.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/control-plane/src/sandbox/provider.ts
  • packages/control-plane/src/sandbox/providers/daytona-provider.ts
  • packages/control-plane/src/session/contracts.test.ts
  • packages/control-plane/src/session/create-session-input.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/session/http/handlers/child-sessions.handler.test.ts
  • packages/control-plane/src/session/http/handlers/session-lifecycle.handler.test.ts
  • packages/control-plane/src/session/identity.test.ts
  • packages/control-plane/src/session/identity.ts
  • packages/control-plane/src/session/integration-settings-resolution.ts
  • packages/control-plane/src/session/repository.test.ts
  • packages/control-plane/src/session/repository.ts
  • packages/control-plane/src/session/runtime-client.test.ts
  • packages/control-plane/src/session/runtime-client.ts
  • packages/control-plane/src/session/schema.ts
  • packages/control-plane/src/session/types.ts
  • packages/control-plane/src/session/websocket-manager.test.ts
  • packages/control-plane/src/storage/object-storage.ts
  • packages/control-plane/test/integration/integration-settings.test.ts
  • packages/control-plane/test/integration/repo-images.test.ts
  • packages/control-plane/tsconfig.json
  • packages/control-plane/vitest.config.ts
  • packages/control-plane/vitest.integration.config.ts
  • packages/modal-infra/.env.example
  • packages/modal-infra/README.md
  • packages/modal-infra/src/app.py
  • packages/modal-infra/src/images/__init__.py
  • packages/modal-infra/src/images/base.py
  • packages/modal-infra/src/sandbox/manager.py
  • packages/modal-infra/src/sandbox/settings.py
  • packages/modal-infra/src/scheduler/image_builder.py
  • packages/modal-infra/src/web_api.py
  • packages/modal-infra/tests/test_build_sandbox.py
  • packages/modal-infra/tests/test_image_builder_v2.py
  • packages/modal-infra/tests/test_sandbox_env_vars.py
  • packages/modal-infra/tests/test_scheduler.py
  • packages/modal-infra/tests/test_ttyd.py
  • packages/modal-infra/tests/test_tunnel_ports.py
  • packages/modal-infra/tests/test_web_api_create_sandbox.py
  • packages/sandbox-runtime/src/sandbox_runtime/docker_service.py
  • packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
  • packages/sandbox-runtime/src/sandbox_runtime/runtime_services.py
  • packages/sandbox-runtime/tests/test_docker_service.py
  • packages/shared/src/types/integrations.test.ts
  • packages/shared/src/types/integrations.ts
  • packages/web/src/components/settings/sandbox-settings-model.ts
  • packages/web/src/components/settings/sandbox-settings.test.tsx
  • packages/web/src/components/settings/sandbox-settings.tsx
  • terraform/README.md
  • terraform/d1/migrations/0022_add_repo_image_profile.sql
  • terraform/environments/production/modal.tf
  • terraform/environments/production/terraform.tfvars.example
  • terraform/environments/production/variables.tf

Comment on lines +44 to +48
const parentSession = await sessionStore.get(parentId);
const parentUserId = parentSession?.userId ?? null;
const childSandboxSettings = parentSession
? await resolveSandboxSettings(env.DB, parentSession.repoOwner, parentSession.repoName)
: {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add null check for parent session.

If the parent session doesn't exist, accessing its properties on lines 46-48 will pass undefined to resolveSandboxSettings. While the spawn context fetch would eventually fail, it's better to return an explicit error early.

🛡️ Proposed fix
 const parentSession = await sessionStore.get(parentId);
+if (!parentSession) {
+  return error("Parent session not found", 404);
+}
 const parentUserId = parentSession?.userId ?? null;
 const childSandboxSettings = parentSession
   ? await resolveSandboxSettings(env.DB, parentSession.repoOwner, parentSession.repoName)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parentSession = await sessionStore.get(parentId);
const parentUserId = parentSession?.userId ?? null;
const childSandboxSettings = parentSession
? await resolveSandboxSettings(env.DB, parentSession.repoOwner, parentSession.repoName)
: {};
const parentSession = await sessionStore.get(parentId);
if (!parentSession) {
return error("Parent session not found", 404);
}
const parentUserId = parentSession?.userId ?? null;
const childSandboxSettings = parentSession
? await resolveSandboxSettings(env.DB, parentSession.repoOwner, parentSession.repoName)
: {};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/routes/session-child-spawn.ts` around lines 44 -
48, The code calls resolveSandboxSettings and accesses parentSession properties
without verifying parentSession exists; update the logic around
sessionStore.get(parentId) to check for a missing parentSession and return an
explicit error (or throw) early instead of proceeding: if parentSession is
null/undefined, do not call resolveSandboxSettings, set parentUserId to null and
respond with a clear error message referencing parentId (or throw), otherwise
keep the existing behavior of computing parentUserId from parentSession.userId
and calling resolveSandboxSettings(parentSession.repoOwner,
parentSession.repoName).

Comment on lines +22 to +34
const body = (await request.json()) as {
content: string;
authorId?: string;
source?: string;
model?: string;
reasoningEffort?: string;
attachments?: Array<{ type: string; name: string; url?: string }>;
callbackContext?: CallbackContext;
};

if (!body.content) {
return error("content is required");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against malformed JSON bodies.

await request.json() throws on an empty or non-JSON body, surfacing as an unhandled error / 500 rather than a clean 400. Wrap the parse and return error(...).

🛡️ Proposed guard
-  const body = (await request.json()) as {
-    content: string;
-    authorId?: string;
-    source?: string;
-    model?: string;
-    reasoningEffort?: string;
-    attachments?: Array<{ type: string; name: string; url?: string }>;
-    callbackContext?: CallbackContext;
-  };
+  let body: {
+    content: string;
+    authorId?: string;
+    source?: string;
+    model?: string;
+    reasoningEffort?: string;
+    attachments?: Array<{ type: string; name: string; url?: string }>;
+    callbackContext?: CallbackContext;
+  };
+  try {
+    body = await request.json();
+  } catch {
+    return error("Invalid JSON body");
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const body = (await request.json()) as {
content: string;
authorId?: string;
source?: string;
model?: string;
reasoningEffort?: string;
attachments?: Array<{ type: string; name: string; url?: string }>;
callbackContext?: CallbackContext;
};
if (!body.content) {
return error("content is required");
}
let body: {
content: string;
authorId?: string;
source?: string;
model?: string;
reasoningEffort?: string;
attachments?: Array<{ type: string; name: string; url?: string }>;
callbackContext?: CallbackContext;
};
try {
body = await request.json();
} catch {
return error("Invalid JSON body");
}
if (!body.content) {
return error("content is required");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/routes/session-prompt.ts` around lines 22 - 34,
The handler currently calls await request.json() directly which throws on empty
or non-JSON bodies; wrap the JSON parse in a try/catch around the request.json()
call in session-prompt.ts (the code that assigns to the body variable) and
return error("invalid JSON body") (or similar 400 response) when parsing fails
or the body is missing, then proceed with the existing check for body.content
(the error("content is required") branch); ensure you catch and handle both
malformed JSON and empty bodies before any further processing.

Comment on lines +20 to +33
const body = (await request.json()) as {
userId: string;
scmUserId?: string;
scmLogin?: string;
scmName?: string;
scmEmail?: string;
scmToken?: string;
scmTokenExpiresAt?: number;
scmRefreshToken?: string;
};

if (!body.userId) {
return error("userId is required");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for a global try/catch or error mapping around handler dispatch in the router.
fd -t f 'router.ts' packages/control-plane/src | xargs -I{} sh -c 'echo "== {} =="; rg -nP -C3 "catch|handler\(|\.json\(\)" {}'

Repository: ColeMurray/background-agents

Length of output: 901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the router dispatch catch block and how it maps thrown errors to responses.
sed -n '360,520p' packages/control-plane/src/router.ts

Repository: ColeMurray/background-agents

Length of output: 2754


Wrap request.json() to return a 4xx on malformed JSON

packages/control-plane/src/router.ts catches any exception thrown by route.handler(...) and always responds with error("Internal server error", 500). Since this handler reads the body with an unguarded await request.json(), malformed JSON will throw and become a 500 instead of a client error.

🛡️ Proposed guard
-  const body = (await request.json()) as {
-    userId: string;
-    scmUserId?: string;
-    scmLogin?: string;
-    scmName?: string;
-    scmEmail?: string;
-    scmToken?: string;
-    scmTokenExpiresAt?: number;
-    scmRefreshToken?: string;
-  };
-
-  if (!body.userId) {
+  let body: {
+    userId: string;
+    scmUserId?: string;
+    scmLogin?: string;
+    scmName?: string;
+    scmEmail?: string;
+    scmToken?: string;
+    scmTokenExpiresAt?: number;
+    scmRefreshToken?: string;
+  };
+  try {
+    body = await request.json();
+  } catch {
+    return error("Invalid JSON body");
+  }
+
+  if (!body.userId) {
     return error("userId is required");
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const body = (await request.json()) as {
userId: string;
scmUserId?: string;
scmLogin?: string;
scmName?: string;
scmEmail?: string;
scmToken?: string;
scmTokenExpiresAt?: number;
scmRefreshToken?: string;
};
if (!body.userId) {
return error("userId is required");
}
let body: {
userId: string;
scmUserId?: string;
scmLogin?: string;
scmName?: string;
scmEmail?: string;
scmToken?: string;
scmTokenExpiresAt?: number;
scmRefreshToken?: string;
};
try {
body = await request.json();
} catch {
return error("Invalid JSON body");
}
if (!body.userId) {
return error("userId is required");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/routes/session-ws-token.ts` around lines 20 - 33,
The handler currently calls await request.json() unguarded so malformed JSON
throws and becomes a 500; wrap the request.json() call in a try/catch around the
assignment to body (the body variable populated from request.json()), and on a
parse error return a client error (use error("Invalid JSON", 400) or similar)
instead of letting the exception propagate; keep the existing shape check for
body.userId afterwards and only proceed when parsing succeeds.

Comment thread packages/modal-infra/src/images/base.py
Comment thread packages/modal-infra/src/sandbox/manager.py Outdated
Comment thread packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py Outdated
@ColeMurray ColeMurray force-pushed the feat/modal-docker-compose-support branch from 2cb61ec to 0a4d3d0 Compare June 1, 2026 18:11
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/sandbox-runtime/src/sandbox_runtime/docker_service.py`:
- Around line 73-83: The _run_command helper can hang because it never applies a
timeout; change _run_command(self, *args: str, check: bool = True,
timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS) to use asyncio.wait_for around
proc.communicate() (asyncio.wait_for(proc.communicate(), timeout_seconds)), and
on asyncio.TimeoutError ensure you terminate/kill the subprocess
(proc.kill()/await proc.wait()) and raise a clear RuntimeError indicating the
command timed out; define DEFAULT_TIMEOUT_SECONDS (or
READY_TIMEOUT_SECONDS_SECONDS per naming rule) as a module-level constant used
by wait_until_ready and other callers instead of repeating literal timeouts, and
update wait_until_ready and any call sites (e.g., where docker info is invoked)
to pass or rely on the new timeout_seconds parameter.
- Around line 138-149: The code currently starts dockerd (self.process) and
spawns asyncio.create_task(self._forward_logs()) but if await
self.wait_until_ready() raises, the dockerd process and the log task are left
running; wrap the wait_until_ready() call in a try/except/finally: capture the
task returned by asyncio.create_task(self._forward_logs()) into a variable
(e.g., self._log_task), then try: await self.wait_until_ready(); except
Exception as e: if self._log_task: self._log_task.cancel(); if self.process:
call self.process.terminate() (or kill()), await self.process.wait() and set
self.process = None; re-raise the exception; on success continue to log
docker.ready. Ensure you reference the existing symbols self.process,
self._forward_logs, wait_until_ready, and docker.ready when making the changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b50e4a1-e19c-4c79-a8cc-c93f62adef5b

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb61ec and 0a4d3d0.

📒 Files selected for processing (61)
  • .github/workflows/terraform.yml
  • README.md
  • docs/GETTING_STARTED.md
  • docs/HOW_IT_WORKS.md
  • docs/IMAGE_PREBUILD.md
  • packages/control-plane/package.json
  • packages/control-plane/src/db/integration-settings.test.ts
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/db/repo-images.test.ts
  • packages/control-plane/src/db/repo-images.ts
  • packages/control-plane/src/routes/repo-images.ts
  • packages/control-plane/src/sandbox/client.test.ts
  • packages/control-plane/src/sandbox/client.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/control-plane/src/sandbox/provider.ts
  • packages/control-plane/src/sandbox/providers/daytona-provider.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/session/http/handlers/child-sessions.handler.test.ts
  • packages/control-plane/src/session/http/handlers/session-lifecycle.handler.test.ts
  • packages/control-plane/src/session/integration-settings-resolution.ts
  • packages/control-plane/src/session/repository.test.ts
  • packages/control-plane/src/session/repository.ts
  • packages/control-plane/src/session/schema.ts
  • packages/control-plane/src/session/types.ts
  • packages/control-plane/src/session/websocket-manager.test.ts
  • packages/control-plane/test/integration/integration-settings.test.ts
  • packages/control-plane/test/integration/repo-images.test.ts
  • packages/control-plane/tsconfig.json
  • packages/control-plane/vitest.config.ts
  • packages/control-plane/vitest.integration.config.ts
  • packages/modal-infra/.env.example
  • packages/modal-infra/README.md
  • packages/modal-infra/src/app.py
  • packages/modal-infra/src/images/__init__.py
  • packages/modal-infra/src/images/base.py
  • packages/modal-infra/src/sandbox/manager.py
  • packages/modal-infra/src/sandbox/settings.py
  • packages/modal-infra/src/scheduler/image_builder.py
  • packages/modal-infra/src/web_api.py
  • packages/modal-infra/tests/test_build_sandbox.py
  • packages/modal-infra/tests/test_image_builder_v2.py
  • packages/modal-infra/tests/test_sandbox_env_vars.py
  • packages/modal-infra/tests/test_scheduler.py
  • packages/modal-infra/tests/test_ttyd.py
  • packages/modal-infra/tests/test_tunnel_ports.py
  • packages/modal-infra/tests/test_web_api_create_sandbox.py
  • packages/sandbox-runtime/src/sandbox_runtime/docker_service.py
  • packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
  • packages/sandbox-runtime/src/sandbox_runtime/runtime_services.py
  • packages/sandbox-runtime/tests/test_docker_service.py
  • packages/shared/src/types/integrations.test.ts
  • packages/shared/src/types/integrations.ts
  • packages/web/src/components/settings/sandbox-settings-model.ts
  • packages/web/src/components/settings/sandbox-settings.test.tsx
  • packages/web/src/components/settings/sandbox-settings.tsx
  • terraform/README.md
  • terraform/d1/migrations/0022_add_repo_image_profile.sql
  • terraform/environments/production/modal.tf
  • terraform/environments/production/terraform.tfvars.example
  • terraform/environments/production/variables.tf
✅ Files skipped from review due to trivial changes (8)
  • packages/modal-infra/.env.example
  • packages/modal-infra/README.md
  • packages/control-plane/src/session/http/handlers/session-lifecycle.handler.test.ts
  • packages/control-plane/tsconfig.json
  • packages/modal-infra/src/app.py
  • packages/modal-infra/src/images/init.py
  • docs/IMAGE_PREBUILD.md
  • docs/HOW_IT_WORKS.md
🚧 Files skipped from review as they are similar to previous changes (48)
  • packages/control-plane/src/session/websocket-manager.test.ts
  • terraform/README.md
  • packages/control-plane/vitest.integration.config.ts
  • terraform/environments/production/terraform.tfvars.example
  • .github/workflows/terraform.yml
  • packages/control-plane/src/sandbox/provider.ts
  • packages/modal-infra/tests/test_build_sandbox.py
  • packages/control-plane/src/session/http/handlers/child-sessions.handler.test.ts
  • packages/control-plane/src/session/repository.test.ts
  • packages/modal-infra/tests/test_image_builder_v2.py
  • packages/control-plane/src/session/types.ts
  • terraform/environments/production/variables.tf
  • terraform/d1/migrations/0022_add_repo_image_profile.sql
  • packages/control-plane/src/sandbox/client.test.ts
  • terraform/environments/production/modal.tf
  • packages/control-plane/src/session/schema.ts
  • packages/sandbox-runtime/src/sandbox_runtime/runtime_services.py
  • packages/control-plane/test/integration/integration-settings.test.ts
  • packages/shared/src/types/integrations.test.ts
  • packages/control-plane/package.json
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/session/integration-settings-resolution.ts
  • packages/control-plane/test/integration/repo-images.test.ts
  • packages/shared/src/types/integrations.ts
  • packages/sandbox-runtime/tests/test_docker_service.py
  • packages/modal-infra/tests/test_ttyd.py
  • packages/modal-infra/src/web_api.py
  • packages/web/src/components/settings/sandbox-settings-model.ts
  • docs/GETTING_STARTED.md
  • packages/control-plane/src/db/repo-images.test.ts
  • packages/modal-infra/tests/test_web_api_create_sandbox.py
  • packages/control-plane/src/routes/repo-images.ts
  • packages/control-plane/vitest.config.ts
  • packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
  • packages/control-plane/src/db/repo-images.ts
  • packages/modal-infra/tests/test_tunnel_ports.py
  • packages/web/src/components/settings/sandbox-settings.test.tsx
  • packages/modal-infra/tests/test_sandbox_env_vars.py
  • packages/modal-infra/src/images/base.py
  • packages/modal-infra/tests/test_scheduler.py
  • packages/control-plane/src/sandbox/client.ts
  • packages/modal-infra/src/scheduler/image_builder.py
  • packages/control-plane/src/db/integration-settings.test.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/modal-infra/src/sandbox/settings.py
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts
  • packages/modal-infra/src/sandbox/manager.py
  • packages/web/src/components/settings/sandbox-settings.tsx

Comment thread packages/sandbox-runtime/src/sandbox_runtime/docker_service.py Outdated
Comment thread packages/sandbox-runtime/src/sandbox_runtime/docker_service.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
packages/modal-infra/src/sandbox/manager.py (2)

688-694: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Update parameter name once build_modal_create_kwargs is fixed.

The timeout= parameter will need to be renamed to timeout_seconds= after the fix in launch_options.py is applied. This is a follow-up to the timeout naming issue in that module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/modal-infra/src/sandbox/manager.py` around lines 688 - 694, The call
building create_kwargs uses the old parameter name timeout=; update the
invocation of build_modal_create_kwargs in manager.py (the create_kwargs
assignment) to pass timeout_seconds=timeout_seconds instead of
timeout=timeout_seconds so it matches the corrected signature in
build_modal_create_kwargs/launch_options code; ensure any other callers in this
file referencing timeout= are similarly updated to use timeout_seconds=.

446-452: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Update parameter name once build_modal_create_kwargs is fixed.

The timeout= parameter will need to be renamed to timeout_seconds= after the fix in launch_options.py is applied. This is a follow-up to the timeout naming issue in that module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/modal-infra/src/sandbox/manager.py` around lines 446 - 452, The call
to build_modal_create_kwargs currently passes timeout=BUILD_TIMEOUT_SECONDS but
that parameter will be renamed to timeout_seconds; update the argument name in
the create_kwargs call to timeout_seconds=BUILD_TIMEOUT_SECONDS (keeping the
same value and other args like
image=select_base_image(launch_options.image_profile), secrets=[],
env_vars=env_vars) so it matches the new build_modal_create_kwargs signature
once launch_options.py is fixed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/control-plane/src/repo-images/build-planner.ts`:
- Around line 38-47: The planMany function assumes
resolveSandboxSettingsForRepos(this.db, repos) returns a settings array aligned
to repos; validate that contract by checking the returned array length and
element correspondence before using sandboxSettings[index] in planMany, e.g.,
after computing sandboxSettings (from resolveSandboxSettingsForRepos and
normalizeSandboxRuntimeSettings) ensure sandboxSettings.length === repos.length
and that no entry is undefined (or add a mapping keyed by repo id to match
entries), and if validation fails throw a clear error or handle the mismatch so
resolveSandboxImageProfile never receives undefined; update planMany (and
related callers if needed) to perform this explicit alignment/validation using
function names resolveSandboxSettingsForRepos, normalizeSandboxRuntimeSettings,
and resolveSandboxImageProfile as reference points.

In `@packages/modal-infra/src/sandbox/launch_options.py`:
- Around line 74-103: Rename the function parameter timeout to timeout_seconds
in build_modal_create_kwargs and use timeout_seconds wherever the previous
timeout variable was referenced (e.g., when setting create_kwargs["timeout"]).
Update the function signature and all callers to pass a value named/treated as
seconds (timeout_seconds) so the unit is encoded per guidelines; keep the
create_kwargs key "timeout" unchanged if the downstream API expects seconds.

In `@packages/modal-infra/src/scheduler/image_builder.py`:
- Around line 59-62: Remove the duplicated parser _parse_image_profile and
instead import and use the existing parse_sandbox_image_profile from
settings.py; update the function references where _parse_image_profile is called
(the two call sites using it) to call parse_sandbox_image_profile, ensure the
imported symbol is added to the module imports, and keep the return type as
SandboxImageProfile where annotated so the type expectations remain unchanged.

---

Duplicate comments:
In `@packages/modal-infra/src/sandbox/manager.py`:
- Around line 688-694: The call building create_kwargs uses the old parameter
name timeout=; update the invocation of build_modal_create_kwargs in manager.py
(the create_kwargs assignment) to pass timeout_seconds=timeout_seconds instead
of timeout=timeout_seconds so it matches the corrected signature in
build_modal_create_kwargs/launch_options code; ensure any other callers in this
file referencing timeout= are similarly updated to use timeout_seconds=.
- Around line 446-452: The call to build_modal_create_kwargs currently passes
timeout=BUILD_TIMEOUT_SECONDS but that parameter will be renamed to
timeout_seconds; update the argument name in the create_kwargs call to
timeout_seconds=BUILD_TIMEOUT_SECONDS (keeping the same value and other args
like image=select_base_image(launch_options.image_profile), secrets=[],
env_vars=env_vars) so it matches the new build_modal_create_kwargs signature
once launch_options.py is fixed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abe7f7f4-6472-42ef-be2b-662c3d831a4f

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4d3d0 and b752ffe.

📒 Files selected for processing (26)
  • packages/control-plane/src/repo-images/build-planner.ts
  • packages/control-plane/src/routes/repo-images.ts
  • packages/control-plane/src/sandbox/client.test.ts
  • packages/control-plane/src/sandbox/client.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/control-plane/src/sandbox/provider.ts
  • packages/control-plane/src/sandbox/providers/daytona-provider.test.ts
  • packages/control-plane/src/sandbox/providers/modal-provider.test.ts
  • packages/control-plane/src/sandbox/providers/modal-provider.ts
  • packages/modal-infra/src/images/base.py
  • packages/modal-infra/src/sandbox/launch_options.py
  • packages/modal-infra/src/sandbox/manager.py
  • packages/modal-infra/src/sandbox/settings.py
  • packages/modal-infra/src/scheduler/image_builder.py
  • packages/modal-infra/src/web_api.py
  • packages/modal-infra/tests/test_build_sandbox.py
  • packages/modal-infra/tests/test_image_builder_v2.py
  • packages/modal-infra/tests/test_sandbox_env_vars.py
  • packages/modal-infra/tests/test_web_api_create_sandbox.py
  • packages/sandbox-runtime/src/sandbox_runtime/docker_service.py
  • packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
  • packages/sandbox-runtime/tests/test_docker_service.py
  • packages/web/src/components/settings/sandbox-settings-model.ts
  • packages/web/src/components/settings/sandbox-settings.test.tsx
  • packages/web/src/components/settings/sandbox-settings.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/control-plane/src/sandbox/providers/daytona-provider.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/control-plane/src/sandbox/client.test.ts
  • packages/modal-infra/tests/test_build_sandbox.py
  • packages/modal-infra/tests/test_web_api_create_sandbox.py
  • packages/modal-infra/src/images/base.py
  • packages/modal-infra/src/web_api.py
  • packages/web/src/components/settings/sandbox-settings-model.ts
  • packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/control-plane/src/sandbox/provider.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts

Comment thread packages/control-plane/src/repo-images/build-planner.ts Outdated
Comment thread packages/modal-infra/src/sandbox/launch_options.py
Comment thread packages/modal-infra/src/scheduler/image_builder.py Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/modal-infra/src/scheduler/image_builder.py (1)

251-307: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix ignored sandbox_settings in build_repo_image

build_repo_image accepts sandbox_settings, but the function body never uses it; it also doesn’t forward it to SandboxManager.create_build_sandbox, which expects settings: SandboxRuntimeSettings | None. Either remove the unused sandbox_settings surface (and update docs/tests accordingly) or pass it through as settings=... when calling create_build_sandbox.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/modal-infra/src/scheduler/image_builder.py` around lines 251 - 307,
The function build_repo_image currently accepts sandbox_settings but never uses
it; update the call to SandboxManager.create_build_sandbox to forward the value
as settings=sandbox_settings (ensuring the type matches SandboxRuntimeSettings |
None) so the passed-in sandbox_settings is applied, and remove any
dead/duplicated doc/test expectations only if you instead choose to drop the
parameter; locate references to sandbox_settings in the build_repo_image
signature and inside the try block around the create_build_sandbox invocation
and add settings=sandbox_settings to that call (or remove the sandbox_settings
parameter entirely and update docs/tests if you opt to delete it).
🧹 Nitpick comments (1)
packages/control-plane/src/repo-images/build-planner.test.ts (1)

11-24: ⚡ Quick win

Add a happy-path test for planMany/plan.

The suite only exercises the misalignment error branch. Consider covering the aligned case so the core repo↔settings↔imageProfile mapping (and resolveSandboxImageProfile per index) is regression-protected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/repo-images/build-planner.test.ts` around lines 11
- 24, Add a happy-path unit test for RepoImageBuildPlanner that verifies
planMany (and optionally plan) correctly maps repos → settings → imageProfile:
mock resolveSandboxSettingsForRepos to return an array of settings whose length
matches the repos array, mock resolveSandboxImageProfile to return distinct
profiles per index, instantiate RepoImageBuildPlanner, call
planner.planMany([...]) and assert the returned plans contain one entry per repo
with the expected imageProfile and settings (and that resolveSandboxImageProfile
was called with the correct index for each repo); you can also add a small
separate test for planner.plan to verify single-repo mapping using the same
mocked helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/modal-infra/src/scheduler/image_builder.py`:
- Around line 251-307: The function build_repo_image currently accepts
sandbox_settings but never uses it; update the call to
SandboxManager.create_build_sandbox to forward the value as
settings=sandbox_settings (ensuring the type matches SandboxRuntimeSettings |
None) so the passed-in sandbox_settings is applied, and remove any
dead/duplicated doc/test expectations only if you instead choose to drop the
parameter; locate references to sandbox_settings in the build_repo_image
signature and inside the try block around the create_build_sandbox invocation
and add settings=sandbox_settings to that call (or remove the sandbox_settings
parameter entirely and update docs/tests if you opt to delete it).

---

Nitpick comments:
In `@packages/control-plane/src/repo-images/build-planner.test.ts`:
- Around line 11-24: Add a happy-path unit test for RepoImageBuildPlanner that
verifies planMany (and optionally plan) correctly maps repos → settings →
imageProfile: mock resolveSandboxSettingsForRepos to return an array of settings
whose length matches the repos array, mock resolveSandboxImageProfile to return
distinct profiles per index, instantiate RepoImageBuildPlanner, call
planner.planMany([...]) and assert the returned plans contain one entry per repo
with the expected imageProfile and settings (and that resolveSandboxImageProfile
was called with the correct index for each repo); you can also add a small
separate test for planner.plan to verify single-repo mapping using the same
mocked helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fef07781-3af4-42a3-aa49-9981ecb94912

📥 Commits

Reviewing files that changed from the base of the PR and between 0faa0da and 73cd23f.

📒 Files selected for processing (5)
  • packages/control-plane/src/repo-images/build-planner.test.ts
  • packages/control-plane/src/repo-images/build-planner.ts
  • packages/modal-infra/src/sandbox/launch_options.py
  • packages/modal-infra/src/sandbox/manager.py
  • packages/modal-infra/src/scheduler/image_builder.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/control-plane/src/repo-images/build-planner.ts
  • packages/modal-infra/src/sandbox/launch_options.py
  • packages/modal-infra/src/sandbox/manager.py

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

sandbox_id=self.sandbox_id,
session_id=session_id,
)
self.runtime_services = RuntimeServices.from_env(self.log)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

this looks like it is included the Docker service regardless of whether the setting is checked. it does appear to be gated within the docker service itself, but this is probably not the ideal approach


while not self.shutdown_event.is_set():
if not await self.runtime_services.ensure_healthy(self._report_fatal_error):
self.shutdown_event.set()
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

should probably log here

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

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.

1 participant