Skip to content

fix modal#16

Merged
hwuiwon merged 9 commits into
mainfrom
modal
Mar 28, 2026
Merged

fix modal#16
hwuiwon merged 9 commits into
mainfrom
modal

Conversation

@hwuiwon
Copy link
Copy Markdown
Collaborator

@hwuiwon hwuiwon commented Mar 27, 2026

Summary by CodeRabbit

  • New Features

    • Added an ASGI deploy entrypoint for Modal with secret support.
  • Refactor

    • Migrated sandbox lifecycle and interactions to async APIs and updated polling/termination flow.
    • Simplified sandbox image provisioning and project sync; adjusted runtime tooling for headless/display use.
  • Documentation

    • Replaced API deployment docs with a Modal deployment guide and updated API usage/details.
  • Chores

    • Removed legacy Docker/Docker Compose artifacts and CONTRIBUTING; adjusted ignore patterns.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8c71e5f-1a41-40f6-8924-00d5b46c5c83

📥 Commits

Reviewing files that changed from the base of the PR and between 07beb8b and 5eb5ed5.

📒 Files selected for processing (2)
  • README.md
  • api/server.py

📝 Walkthrough

Walkthrough

Introduces a Modal-based deployment: adds a Modal ASGI entrypoint and Modal image for the FastAPI server, converts sandbox creation and lifecycle to Modal async APIs, rewrites the sandbox image build and project sync, and removes local Docker artifacts and CONTRIBUTING.md.

Changes

Cohort / File(s) Summary
Modal ASGI server & async sandbox wiring
api/server.py
Added modal_app and a Modal ASGI serve() entrypoint; replaced synchronous sandbox interactions with async Modal APIs (poll.aio(), tunnels.aio(), terminate.aio()), made _cleanup_finished_sandbox async, and updated endpoints to await sandbox operations.
Sandbox image & async factory
sandbox/image.py
Rewrote sandbox image build to remove direct browser apt installs, add desktop/display tooling, fonts, and Patchright Chromium runtime; switched to a single project sync/mount into /opt/cua with ignore predicate; converted create_cua_sandbox to async def and use modal.Sandbox.create.aio().
Removed local Docker artifacts
.dockerignore, Dockerfile, docker-compose.yml
Cleared .dockerignore patterns, deleted Dockerfile, and removed the cua service from docker-compose.yml.
Repository metadata
CONTRIBUTING.md
Deleted CONTRIBUTING.md.
Minor VCS ignore tweak
.gitignore
Adjusted Python bytecode ignore from __pycache__/ to __pycache__.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FastAPI as FastAPI (web_app / serve)
  participant ModalImage as Modal Image (api_image)
  participant ModalSandbox as Modal Sandbox

  Client->>FastAPI: POST /runs (create)
  FastAPI->>ModalImage: ensure image / start as needed
  FastAPI->>ModalSandbox: await create_cua_sandbox(...)
  ModalSandbox-->>FastAPI: sandbox handle
  FastAPI->>ModalSandbox: await sandbox.tunnels.aio() / poll.aio()
  FastAPI->>Client: stream events / return status
  Client->>FastAPI: POST /runs/{id}/stop
  FastAPI->>ModalSandbox: await handle.sandbox.terminate.aio()
  ModalSandbox-->>FastAPI: terminated
  FastAPI->>Client: final status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • fix modal #16 — Appears to modify the same server and sandbox async wiring (Modal ASGI entrypoint and create_cua_sandbox async conversion).
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix modal' is vague and does not clearly convey the specific changes made in this comprehensive refactoring that converts Docker-based deployment to Modal serverless infrastructure. Use a more descriptive title such as 'Migrate from Docker to Modal serverless deployment' or 'Convert Docker-based setup to Modal async infrastructure' to better reflect the scope and nature of the changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch modal

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

🧹 Nitpick comments (1)
sandbox/image.py (1)

79-86: Update docstring to reflect async API.

The docstring references sandbox.tunnels() but callers should now use await sandbox.tunnels.aio() to match the async pattern. Consider updating for consistency.

📝 Suggested docstring update
     """Create a Modal sandbox configured for a CUA run.
 
     Returns the sandbox immediately — startup is asynchronous.
-    Use sandbox.tunnels() to get the status API URL.
+    Use ``await sandbox.tunnels.aio()`` to get the status API URL.
 
     ``extra_env`` is merged into the sandbox environment — used to propagate
     OTel trace context (TRACEPARENT, TRACESTATE) and OTel config vars.
     """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sandbox/image.py` around lines 79 - 86, Update the docstring to reflect the
async tunnels API: replace guidance that callers should use sandbox.tunnels()
with instructions to await the async accessor using await sandbox.tunnels.aio(),
and mention that startup remains asynchronous and sandbox.tunnels.aio() returns
the status API URL; update any phrasing around synchronous usage to indicate the
new async pattern and keep the note about extra_env merging for trace context
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@sandbox/image.py`:
- Around line 79-86: Update the docstring to reflect the async tunnels API:
replace guidance that callers should use sandbox.tunnels() with instructions to
await the async accessor using await sandbox.tunnels.aio(), and mention that
startup remains asynchronous and sandbox.tunnels.aio() returns the status API
URL; update any phrasing around synchronous usage to indicate the new async
pattern and keep the note about extra_env merging for trace context unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83d4e427-19aa-4b5f-9d42-132ffbb05cb1

📥 Commits

Reviewing files that changed from the base of the PR and between 074dc3f and 8e47da8.

📒 Files selected for processing (2)
  • api/server.py
  • sandbox/image.py

hwuiwon added 4 commits March 27, 2026 17:29
Replace 15+ individual add_local_dir/add_local_file calls with one
add_local_dir of the project root per image, using ignore patterns
to exclude .git, .venv, tests, __pycache__, etc.

This follows Modal's recommended pattern from their docs and reduces
the mount list from 15+ entries to 2.
Deployment is fully on Modal now. Remove Docker references from
image ignore lists too.
…ores

- Remove _sandbox_image_warmup function (no longer needed)
- Add CUA_API_KEY + ENVIRONMENT to llm-secret for auth in production
- Use ~FilePatternMatcher allowlist + explicit dir excludes for image mounts
- Exclude output/, tests/, llm/, .git/, playbooks/definitions/ from images
- Include only source extensions: .py .js .json .yaml .yml .toml .lock .sh
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.

🧹 Nitpick comments (1)
api/server.py (1)

132-150: Consider adding synchronization to prevent TOCTOU race conditions.

With async operations, there's a time-of-check to time-of-use gap: two concurrent requests can both retrieve the same handle at line 133, both pass the None check, and both proceed to poll/remove. While currently benign (the removal uses dict.pop with default), this pattern becomes problematic if future changes add operations like terminate to cleanup.

The InMemoryRunRegistry (per context snippet 2) has no synchronization primitives. Consider using an asyncio.Lock per run_id or a registry-level lock for atomic check-and-remove operations.

Example approach using a lock
# In InMemoryRunRegistry or at module level
_cleanup_locks: dict[str, asyncio.Lock] = {}

async def _cleanup_finished_sandbox(run_id: str) -> bool:
    lock = _cleanup_locks.setdefault(run_id, asyncio.Lock())
    async with lock:
        handle = _run_registry.get(run_id)
        if handle is None:
            return False
        try:
            exit_code = await handle.sandbox.poll.aio()
        except Exception:
            log.exception("Failed to poll sandbox for run %s", run_id)
            return False
        if exit_code is None:
            return False
        log.info("Cleaning up finished sandbox for run %s (exit code %s)", run_id, exit_code)
        _remove_run_registry(run_id)
        return True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/server.py` around lines 132 - 150, The cleanup function
_cleanup_finished_sandbox currently reads from _run_registry then acts on the
handle, creating a TOCTOU race; fix by introducing synchronization (either an
asyncio.Lock per run_id or a registry-level asyncio.Lock) so the
get/poll/check/pop sequence is atomic: create a lock map (e.g., _cleanup_locks:
dict[str, asyncio.Lock]) or add locking into InMemoryRunRegistry, acquire the
appropriate lock before retrieving handle, await handle.sandbox.poll.aio() while
holding the lock (or use a registry method that atomically pops if finished),
then call _remove_run_registry(run_id) inside the lock and release; update
_cleanup_finished_sandbox, InMemoryRunRegistry (or _remove_run_registry)
accordingly to ensure no concurrent cleanup/terminate interleavings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api/server.py`:
- Around line 132-150: The cleanup function _cleanup_finished_sandbox currently
reads from _run_registry then acts on the handle, creating a TOCTOU race; fix by
introducing synchronization (either an asyncio.Lock per run_id or a
registry-level asyncio.Lock) so the get/poll/check/pop sequence is atomic:
create a lock map (e.g., _cleanup_locks: dict[str, asyncio.Lock]) or add locking
into InMemoryRunRegistry, acquire the appropriate lock before retrieving handle,
await handle.sandbox.poll.aio() while holding the lock (or use a registry method
that atomically pops if finished), then call _remove_run_registry(run_id) inside
the lock and release; update _cleanup_finished_sandbox, InMemoryRunRegistry (or
_remove_run_registry) accordingly to ensure no concurrent cleanup/terminate
interleavings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0038ff31-daeb-424e-a709-5000da34016f

📥 Commits

Reviewing files that changed from the base of the PR and between 8e47da8 and 90c89cf.

📒 Files selected for processing (7)
  • .dockerignore
  • .gitignore
  • CONTRIBUTING.md
  • Dockerfile
  • api/server.py
  • docker-compose.yml
  • sandbox/image.py
💤 Files with no reviewable changes (4)
  • CONTRIBUTING.md
  • .dockerignore
  • docker-compose.yml
  • Dockerfile
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • sandbox/image.py

hwuiwon added 4 commits March 27, 2026 21:13
- Move image and secrets to modal.App() constructor (like echo pattern)
- Add uv_sync extra_options='--no-dev' to skip test dependencies
- Rename FastAPI app → web_app to avoid modal_app collision
- Remove redundant per-function image/secrets config
- Add DEBIAN_FRONTEND=noninteractive before apt-get in sandbox image
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