feat(sandbox-code-runner): run Node and Python in iii-sandbox microVMs, with the iii SDK inside the guest - #728
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 34 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (77)
📝 WalkthroughWalkthroughThis PR adds the ChangesSandbox code runner and console redaction
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant Console
participant sandbox-code-runner
participant RuntimeManager
participant iii-sandbox
Caller->>sandbox-code-runner: trigger sandbox-code-runner::run
sandbox-code-runner->>RuntimeManager: run(RunRequest)
RuntimeManager->>iii-sandbox: create or reuse runtime
RuntimeManager->>iii-sandbox: plant files and execute wrapper
iii-sandbox-->>RuntimeManager: logs and framed result
RuntimeManager-->>sandbox-code-runner: RunResponse
sandbox-code-runner-->>Console: function-trigger message
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
An injected function-trigger renderer can hide a capability inside its own card and still leak it: the card's `raw json` tab renders `message.input` / `message.output` verbatim, the copy buttons put the same value on the clipboard, an assistant-turn copy re-serializes every call's arguments, and the trace span tabs show the same payload from the other side. Adds `redactRaw?(value: unknown): unknown` to the `FunctionTriggerRenderer` contract: the claiming renderer declares how to redact, and the host applies it once at each of those exits. It runs inside the host's render, so it must be pure and total; a throw is fenced and fails CLOSED — the pane shows a placeholder rather than the raw value. Trace-side tabs get the same treatment, and `SpanPanel.redaction-coverage.test.ts` enforces a closed world: every tab either wires a redactor or carries a written reason why it cannot leak. Needed by any worker whose function arguments carry a capability — the first is code-runner, whose `runtime_id` addresses a live microVM.
A worker that executes nothing itself: every eval and every registered handler call becomes `sandbox::*` calls to the iii-sandbox daemon, on the `node` and `python` preset images. That buys Python, npm/pip, and a real OS per call, and keeps the host filesystem untouched. Three functions, split by lifetime: - `code-runner::eval` is one-shot — `sandbox::run` boots, runs, and stops the VM in a single call, returning no `runtime_id` because nothing survives to address. `keep: true` mints one; passing an existing `runtime_id` reuses that VM and leaves it running. - `code-runner::register_function` is persistent: it creates one runtime per `(namespace, lang)` and publishes the source as a bus function. - `code-runner::teardown` takes a `runtime_id` or a whole namespace. A `runtime_id` is a capability — it addresses a live VM — so it never reaches a caller that does not already hold it: the error types redact it, and the injected console UI declares `redactRaw` so the card, its raw pane, and the clipboard are covered too. The handler-to-runner protocol frames results with a per-call sentinel carried in a stdin envelope, never argv. The sentinel is a framing device, not a security boundary — the handler loads into the runner's own process and can intercept stdout to forge a frame; the doc comment says so plainly. Missing iii-sandbox is not fatal: the worker warns at boot and keeps serving, failing each call with a clear message.
… codes, console UI
…lign guest filenames
…e2e integration test
Four name-only leftovers from the code-runner -> sandbox-code-runner rename, caught by a path-safe repo grep gate (the original gate's filter matched the whole path:line, which hid every hit under sandbox-code-runner/ itself): - README.md:156 documented the guest worker identity as code-runner:eval / code-runner:<function_id>; the real values (verified against src/manager.rs) are sandbox-code-runner:run and sandbox-code-runner:<function_id>. - build.rs:1 doc comment. - ui/build.mjs:35 and :80 comments. Prose-only; surrounding lines rewrapped to the existing width where the longer name pushed past it. CODE_RUNNER_GUIDANCE (a Rust identifier) is deliberately left as-is, deferred with the rest of the CodeRunner*/ CODE_RUNNER_* identifier family to the whole-branch review.
…rding after the rename Final review fix wave for the eval->run rename: two README claims that contradicted the code (the namespace runtime's network access, and a dead invalid_request cause), stale "eval" wording left in the guest-script string constants that get planted into every tenant microVM, a doc comment naming the one Python filename the design forbids (iii.py), a stale test rationale comment plus a redundant needle, two awkward published function descriptions, a test doc/body contradiction, missing plant-table test coverage on the Node arm, and an undocumented size-ceiling tension in the guest SDK build script.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
sandbox-code-runner/tests/integration.rs (1)
73-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winIsolate the engine state directory from the developer's real home.
The test creates a scratch
homeand removes it on drop, but it only passes that directory as the config path. The engine still resolves its state directory from the process environment. State then lands outsidehome, survivesCleanup, and can leak between runs. Set the home environment for the spawned engine.♻️ Proposed change
std::process::Command::new(&iii_bin) .arg("-c") .arg(&cfg_path) .arg("--no-update-check") + .env("HOME", home) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null())🤖 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 `@sandbox-code-runner/tests/integration.rs` around lines 73 - 81, Update the spawned engine command in the integration test to set its home-directory environment variable to the scratch home used by the test before calling spawn. Keep the existing config argument and cleanup flow unchanged so all engine state is created under that temporary directory.sandbox-code-runner/src/manager.rs (1)
1249-1298: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the remaining
evalidentifiers in the tests.The public op is
run, but the test helpers and section headers still useeval(eval_req,an_ephemeral_eval_..., "eval: the boot paths"). Renaming them keeps the test vocabulary aligned with the wire surface.🤖 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 `@sandbox-code-runner/src/manager.rs` around lines 1249 - 1298, Rename the remaining test-only eval terminology to run, including the eval_req helper, an_ephemeral_eval_* test names, and the “eval: the boot paths” section header. Keep the test behavior and public operation unchanged while aligning identifiers and comments with the run wire operation.sandbox-code-runner/tests/runner_exec.rs (1)
106-118: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winWrite stdin on a thread so a full stdout pipe cannot hang the suite.
write_allruns beforewait_boundedstarts the stdout and stderr drain threads. If a future test feeds an envelope larger than the pipe buffer while the child writes enough output to fill its own stdout pipe, both sides block andwrite_allhas no deadline. The 15 s cap inwait_boundedis never reached in that case.♻️ Proposed change
- child - .stdin - .take() - .unwrap() - .write_all(stdin.as_bytes()) - .unwrap(); + let mut sink = child.stdin.take().unwrap(); + let bytes = stdin.as_bytes().to_vec(); + let feeder = std::thread::spawn(move || sink.write_all(&bytes)); let out = wait_bounded(child, 15); + feeder.join().unwrap().unwrap();🤖 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 `@sandbox-code-runner/tests/runner_exec.rs` around lines 106 - 118, Update the test process flow around wait_bounded so writing the child’s stdin occurs on a separate thread, allowing stdout and stderr draining to begin concurrently. Join or otherwise handle the stdin writer while preserving the existing 15-second bounded wait and error propagation for write failures.sandbox-code-runner/ui/src/lib/shared.tsx (1)
391-409: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMake
errorInforeturn a real string for every input shape.
isErrorOutputonly checks that theerrorkey exists. Ifrec.errorisundefined,JSON.stringify(err)returnsundefined, somessageis typedstringbut isundefinedat runtime.ErrorCardthen callsredactRuntimeIds(message), which throws aTypeError. The host fences the throw, so the card is replaced by an error chip instead of the intended redacted card.♻️ Proposed fix
const message = typeof err === 'string' ? err : typeof errObj?.message === 'string' ? errObj.message - : JSON.stringify(err) + : (JSON.stringify(err) ?? 'the response carried an error with no message') return { message }🤖 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 `@sandbox-code-runner/ui/src/lib/shared.tsx` around lines 391 - 409, Update errorInfo so its returned message is always a string, including when rec.error is undefined or JSON.stringify(err) produces undefined. Preserve the existing string and object-message handling, and add a safe fallback before returning message so ErrorCard can always pass it to redactRuntimeIds.
🤖 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 `@sandbox-code-runner/src/config.rs`:
- Around line 59-65: Update clamp_timeout so the default_timeout_ms path is also
capped at max_timeout_ms, ensuring omitted and explicit timeout requests never
exceed the configured maximum.
In `@sandbox-code-runner/src/functions/register.rs`:
- Around line 23-25: The documentation in
sandbox-code-runner/src/functions/register.rs lines 23-25 must state that Node
and Python registrations may share a namespace, with runtime reuse scoped to the
same (namespace, language) pair. Update sandbox-code-runner/src/functions/mod.rs
lines 48-55 to remove the claim that the first registration fixes the namespace
language and describe reuse as scoped to the namespace and language; no
behavioral code change is required.
In
`@sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.register_function.json`:
- Line 34: Align the per-(namespace, lang) runtime rule across the documented
and tested surfaces: update the generated lang description in
sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.register_function.json#L34-L34
to say later registrations must keep the same namespace and lang, refresh the
schema output, and change the wording in sandbox-code-runner/README.md#L98-L100
to match that rule. In sandbox-code-runner/tests/integration.rs#L411-L424, add
or adjust the e2e assertion so a single namespace can register multiple runtimes
when the lang differs, including a case like ce-e2e-iii::describe with lang set
to python.
In `@sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.json`:
- Around line 7-24: Update the TeardownRequest JSON Schema definition and its
golden snapshot to add a oneOf constraint requiring exactly one of runtime_id or
namespace. Ensure validation rejects requests with neither selector or both
selectors while preserving the existing nullable string property definitions.
In `@sandbox-code-runner/tests/integration.rs`:
- Around line 129-136: Update the sandbox collection logic in the integration
test helper around the resp["sandboxes"] pipeline so `stopped` is read with
`as_bool()` before filtering, and only keep entries whose boolean value is
explicitly false. If `stopped` is missing or not a boolean, make the test fail
with the existing response-shape expectation instead of treating it as live, and
keep the `sandbox_id` extraction path unchanged.
In `@sandbox-code-runner/ui/styles.css`:
- Around line 59-65: Update the reduced-motion styles near .cr-ui-msg-note.pulse
by adding a prefers-reduced-motion: reduce media rule that sets animation: none
for the selector, while preserving the existing pulse animation for users
without the preference.
---
Nitpick comments:
In `@sandbox-code-runner/src/manager.rs`:
- Around line 1249-1298: Rename the remaining test-only eval terminology to run,
including the eval_req helper, an_ephemeral_eval_* test names, and the “eval:
the boot paths” section header. Keep the test behavior and public operation
unchanged while aligning identifiers and comments with the run wire operation.
In `@sandbox-code-runner/tests/integration.rs`:
- Around line 73-81: Update the spawned engine command in the integration test
to set its home-directory environment variable to the scratch home used by the
test before calling spawn. Keep the existing config argument and cleanup flow
unchanged so all engine state is created under that temporary directory.
In `@sandbox-code-runner/tests/runner_exec.rs`:
- Around line 106-118: Update the test process flow around wait_bounded so
writing the child’s stdin occurs on a separate thread, allowing stdout and
stderr draining to begin concurrently. Join or otherwise handle the stdin writer
while preserving the existing 15-second bounded wait and error propagation for
write failures.
In `@sandbox-code-runner/ui/src/lib/shared.tsx`:
- Around line 391-409: Update errorInfo so its returned message is always a
string, including when rec.error is undefined or JSON.stringify(err) produces
undefined. Preserve the existing string and object-message handling, and add a
safe fallback before returning message so ErrorCard can always pass it to
redactRuntimeIds.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9ad4e945-6803-464a-824f-02ea1e9fbd72
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsandbox-code-runner/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (78)
.github/workflows/create-tag.yml.github/workflows/release.ymlREADME.mdconsole/SKILL.mdconsole/web/src/components/chat/MessageList.tsxconsole/web/src/components/function-trigger/FunctionTriggerCard.tsxconsole/web/src/components/function-trigger/redact-raw.test.tsxconsole/web/src/components/function-trigger/renderer-registry.tsxconsole/web/src/lib/function-trigger-copy.test.tsconsole/web/src/lib/function-trigger-copy.tsconsole/web/src/pages/TracesV2/components/SpanBaggageTab.tsxconsole/web/src/pages/TracesV2/components/SpanErrorsTab.test.tsxconsole/web/src/pages/TracesV2/components/SpanErrorsTab.tsxconsole/web/src/pages/TracesV2/components/SpanLinksTab.tsxconsole/web/src/pages/TracesV2/components/SpanLogsTab.test.tsxconsole/web/src/pages/TracesV2/components/SpanLogsTab.tsxconsole/web/src/pages/TracesV2/components/SpanOtelLogsTab.tsxconsole/web/src/pages/TracesV2/components/SpanPanel.redaction-coverage.test.tsconsole/web/src/pages/TracesV2/components/SpanPanel.tsxconsole/web/src/pages/TracesV2/components/SpanTagsTab.test.tsxconsole/web/src/pages/TracesV2/components/SpanTagsTab.tsxconsole/web/src/pages/TracesV2/lib/functionTriggerFromSpan.test.tsconsole/web/src/pages/TracesV2/lib/functionTriggerFromSpan.tsconsole/web/src/pages/TracesV2/lib/redactAttributes.test.tsconsole/web/src/pages/TracesV2/lib/redactAttributes.tsconsole/web/src/types/injectable-ui.tsdocs/sops/injectable-console-ui.mdpackages/console-ui/index.d.tspnpm-workspace.yamlsandbox-code-runner/Cargo.tomlsandbox-code-runner/README.mdsandbox-code-runner/build.rssandbox-code-runner/config.yamlsandbox-code-runner/iii.worker.yamlsandbox-code-runner/src/config.rssandbox-code-runner/src/engine.rssandbox-code-runner/src/error.rssandbox-code-runner/src/functions/inject_guidance.rssandbox-code-runner/src/functions/mod.rssandbox-code-runner/src/functions/register.rssandbox-code-runner/src/functions/run.rssandbox-code-runner/src/functions/teardown.rssandbox-code-runner/src/lib.rssandbox-code-runner/src/main.rssandbox-code-runner/src/manager.rssandbox-code-runner/src/manifest.rssandbox-code-runner/src/runner.rssandbox-code-runner/src/ui.rssandbox-code-runner/tests/golden/runners/iii.mjssandbox-code-runner/tests/golden/runners/invoke.mjssandbox-code-runner/tests/golden/runners/invoke.pysandbox-code-runner/tests/golden/runners/run.mjssandbox-code-runner/tests/golden/runners/run.pysandbox-code-runner/tests/golden/runners/sandbox_code_runner_iii.pysandbox-code-runner/tests/golden/schemas/sandbox-code-runner.inject-guidance.jsonsandbox-code-runner/tests/golden/schemas/sandbox-code-runner.register_function.jsonsandbox-code-runner/tests/golden/schemas/sandbox-code-runner.run.jsonsandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.jsonsandbox-code-runner/tests/integration.rssandbox-code-runner/tests/manifest.rssandbox-code-runner/tests/runner_exec.rssandbox-code-runner/tests/schemas.rssandbox-code-runner/tests/support/mod.rssandbox-code-runner/ui/build.mjssandbox-code-runner/ui/package.jsonsandbox-code-runner/ui/page.tsxsandbox-code-runner/ui/src/function-trigger-message/index.tsxsandbox-code-runner/ui/src/function-trigger-message/redact-runtime-ids.test.tsxsandbox-code-runner/ui/src/function-trigger-message/register-function.test.tsxsandbox-code-runner/ui/src/function-trigger-message/register-function.tsxsandbox-code-runner/ui/src/function-trigger-message/run.test.tsxsandbox-code-runner/ui/src/function-trigger-message/run.tsxsandbox-code-runner/ui/src/function-trigger-message/teardown.test.tsxsandbox-code-runner/ui/src/function-trigger-message/teardown.tsxsandbox-code-runner/ui/src/lib/shared.test.tsxsandbox-code-runner/ui/src/lib/shared.tsxsandbox-code-runner/ui/styles.csssandbox-code-runner/ui/tsconfig.json
| /// Which runner backs this namespace: "node" or "python". A namespace's | ||
| /// language is fixed by its first registration; a later id under the | ||
| /// same namespace but a different lang is refused. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Allow both languages in one namespace.
These descriptions state that the first registration fixes a namespace language. The worker contract uses a persistent runtime per (namespace, language). TeardownResponse also supports more than one runtime for a namespace. This publishes an incorrect rejection rule to callers.
sandbox-code-runner/src/functions/register.rs#L23-L25: State that Node and Python registrations can share a namespace, with reuse scoped to the same(namespace, language)pair.sandbox-code-runner/src/functions/mod.rs#L48-L55: Remove the claim that later IDs must use the first registration language. Describe reuse as scoped to the same namespace and language.
📍 Affects 2 files
sandbox-code-runner/src/functions/register.rs#L23-L25(this comment)sandbox-code-runner/src/functions/mod.rs#L48-L55
🤖 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 `@sandbox-code-runner/src/functions/register.rs` around lines 23 - 25, The
documentation in sandbox-code-runner/src/functions/register.rs lines 23-25 must
state that Node and Python registrations may share a namespace, with runtime
reuse scoped to the same (namespace, language) pair. Update
sandbox-code-runner/src/functions/mod.rs lines 48-55 to remove the claim that
the first registration fixes the namespace language and describe reuse as scoped
to the namespace and language; no behavioral code change is required.
| "properties": { | ||
| "namespace": { | ||
| "default": null, | ||
| "type": [ | ||
| "string", | ||
| "null" | ||
| ] | ||
| }, | ||
| "runtime_id": { | ||
| "default": null, | ||
| "type": [ | ||
| "string", | ||
| "null" | ||
| ] | ||
| } | ||
| }, | ||
| "title": "TeardownRequest", | ||
| "type": "object" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate relevant files =="
git ls-files | rg 'sandbox-code-runner|teardown|schema' || true
echo
echo "== target JSON =="
if [ -f sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.json ]; then
cat -n sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.json
fi
echo
echo "== references to teardown request/schema/description =="
rg -n "TeardownRequest|teardown\.json|runtime_id|namespace.*runtime_id|Exactly one|selector" sandbox-code-runner -S || trueRepository: iii-hq/workers
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== teardown source =="
cat -n sandbox-code-runner/src/functions/teardown.rs
echo
echo "== golden schema validation reference =="
sed -n '1,220p' sandbox-code-runner/tests/schemas.rs
cat -n sandbox-code-runner/tests/schemas.rs | sed -n '1,220p'
echo
echo "== schema validation behavior probe from Rust jsonschema dependency if locked; otherwise inspect Cargo.toml =="
sed -n '1,220p' sandbox-code-runner/Cargo.toml
rg -n 'jsonschema|validate' sandbox-code-runner/Cargo.toml sandbox-codeRunner/Cargo.lock .github/scripts/tests . 2>/dev/null | head -n 200 || true
echo
echo "== JSON object examples matching current schema =="
python3 - <<'PY'
import json
schema = json.load(open('sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.json'))
props = schema['request_schema']['properties']
examples = [{}, {"runtime_id":"r","namespace":"n"},{"namespace":"n"},{"runtime_id":"r"},{"namespace":None},{"runtime_id":None}]
for ex in examples:
valid = set(ex) <= set(props) and all(e in (props[k]['type'] if isinstance(props[k]['type'], list) else [props[k]['type']]) for k,e in ex.items())
print(json.dumps(ex, separators=(',', ':')), "schema_valid_by_type_and_additional_items_without_required_or_oneOf=", valid)
PYRepository: iii-hq/workers
Length of output: 33855
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== teardown implementation =="
sed -n '1,260p' sandbox-code-runner/src/functions/teardown.rs
echo
echo "== search for invalid request handling near teardown =="
rg -n "TeardownRequest|InvalidRequest|runtime_id|namespace|both|neither|exactly one|teardown" sandbox-code-runner/src -S
echo
echo "== registry/SDK validation path references =="
rg -n "request_schema|response_schema|validate_payload|Validate|validate_request|JsonSchema|Schema|unknown.*schema|invalid_request|invalid" sandbox-code-runner src crates iii-sdk 2>/dev/null | head -n 240 || trueRepository: iii-hq/workers
Length of output: 50370
Enforce the exclusive selector requirement in the teardown request schema.
TeardownRequest serializes { runtime_id: Option<String>, namespace: Option<String> }, so the generated request_schema accepts no selector and both selectors. Add the matching oneOf constraint to the emitted schema snapshot and TeardownRequest's derived JSON Schema shape so schema validation rejects those requests before runtime or client handling.
🤖 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 `@sandbox-code-runner/tests/golden/schemas/sandbox-code-runner.teardown.json`
around lines 7 - 24, Update the TeardownRequest JSON Schema definition and its
golden snapshot to add a oneOf constraint requiring exactly one of runtime_id or
namespace. Ensure validation rejects requests with neither selector or both
selectors while preserving the existing nullable string property definitions.
| resp["sandboxes"] | ||
| .as_array() | ||
| .expect("sandboxes is an array") | ||
| .iter() | ||
| .filter(|s| s["stopped"] == false) | ||
| .filter_map(|s| s["sandbox_id"].as_str().map(str::to_string)) | ||
| .collect() | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect the sandbox::list response shape for the `stopped` field.
set -uo pipefail
rg -nP -C4 '"?stopped"?' --type=rust -g '!sandbox-code-runner/**' | head -80
rg -nP -C6 'sandbox::list' --type=rust | head -80Repository: iii-hq/workers
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -uo pipefail
echo "Files named integration.rs:"
fd -a 'integration\.rs$' . | sed 's#^\./##'
echo
echo "sandbox-code-runner tests around relevant lines:"
if [ -f sandbox-code-runner/tests/integration.rs ]; then
wc -l sandbox-code-runner/tests/integration.rs
sed -n '110,145p' sandbox-code-runner/tests/integration.rs | cat -n -v
fi
echo
echo "Search for sandbox::list and stopped in repository:"
rg -n -C3 'sandbox::list|\.stopped|\"stopped\"|stopped:' . --glob '!target/**' --glob '!node_modules/**' | head -240Repository: iii-hq/workers
Length of output: 19380
🏁 Script executed:
#!/bin/bash
set -uo pipefail
echo "Locate sandbox crate files:"
git ls-files | rg '(^|/)sandbox|manager\.rs$|registry|sandbox-code-runner' | sed -n '1,200p'
echo
echo "Search sandbox::list implementation in sandbox-code-runner (bounded):"
rg -n -C5 'sandbox::list|List|list|sandboxes|stopped' sandbox-code-runner/src || true
echo
echo "Show manager.rs relevant definitions around sandbox registration/listing (bounded):"
rg -n 'struct .*Sandbox|SandboxRegistry|fn .*list|let .*stopped|stopped' sandbox-code-runner/src/*.rs | head -200
echo
echo "Inspect candidate manager.rs sections:"
sed -n '1,260p' sandbox-code-runner/src/manager.rs | cat -n -v
sed -n '260,460p' sandbox-code-runner/src/manager.rs | cat -n -vRepository: iii-hq/workers
Length of output: 50370
Consume stopped as a boolean before filtering.
s["stopped"] == false also matches a missing field (Null) because Null != false turns the filter off. Check as_bool() explicitly and fail on an unexpected response shape so the leak assertions do not read an empty live-id set.
🤖 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 `@sandbox-code-runner/tests/integration.rs` around lines 129 - 136, Update the
sandbox collection logic in the integration test helper around the
resp["sandboxes"] pipeline so `stopped` is read with `as_bool()` before
filtering, and only keep entries whose boolean value is explicitly false. If
`stopped` is missing or not a boolean, make the test fail with the existing
response-shape expectation instead of treating it as live, and keep the
`sandbox_id` extraction path unchanged.
| [data-iii-ui="sandbox-code-runner"] .cr-ui-msg-note.pulse { | ||
| animation: cr-ui-pulse 1.6s ease-in-out infinite; | ||
| } | ||
| @keyframes cr-ui-pulse { | ||
| 0%, 100% { opacity: 1 } | ||
| 50% { opacity: 0.35 } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | sed -n '1,200p'
echo "== target file excerpt =="
if [ -f sandbox-code-runner/ui/styles.css ]; then
wc -l sandbox-code-runner/ui/styles.css
sed -n '1,120p' sandbox-code-runner/ui/styles.css | cat -n
else
fd -a styles.css .
fi
echo "== pulse/iii-ui references =="
rg -n "data-iii-ui|cr-ui-msg-note|cr-ui-pulse|prefers-reduced-motion|animation" sandbox-code-runner/ui sandbox-code-runner 2>/dev/null | sed -n '1,200p'Repository: iii-hq/workers
Length of output: 30115
Honor the reduced-motion user preference for .cr-ui-msg-note.pulse.
The infinite cr-ui-pulse animation runs while messages are pending. Add a prefers-reduced-motion: reduce rule that sets animation: none for this selector so users who request reduced motion do not see the pulse.
🤖 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 `@sandbox-code-runner/ui/styles.css` around lines 59 - 65, Update the
reduced-motion styles near .cr-ui-msg-note.pulse by adding a
prefers-reduced-motion: reduce media rule that sets animation: none for the
selector, while preserving the existing pulse animation for users without the
preference.
ea56acb to
4903e15
Compare
skill-check — worker0 verified, 55 skipped (no docs/).
Four for four. Nicely done. |
Adds
sandbox-code-runner, a worker that runs Node.js and Python inside iii-sandboxmicroVMs, and gives the code running in those VMs a real
iiiSDK client.Related to MOT-3971 ("Run code in a sandboxed environment"). Not a duplicate, and
this PR does not close it: MOT-3971's technical notes describe
eval::*hosted on theshell worker, session-keyed on
fs_scope.root, with a warm pool and no guest network.This is a standalone worker with a different surface and no session binding. It does
deliver the "later stage" that ticket anticipates — "lets the running code call other iii
functions directly". Happy to retarget this at a dedicated ticket if you'd prefer one.
What's here
The worker —
sandbox-code-runner::run,::register_function,::teardown.runis one-shot by default (boot, run, destroy, noruntime_idback);keep: truemints a runtime you own; passing
runtime_idreuses that VM's filesystem.register_functionpublishes a bus function whose handler executes in a persistentruntime, one per
(namespace, lang). It executes nothing itself — every run and everyhandler call becomes
sandbox::*calls over the bus.The guest
iiiglobal — evaluated code and registered handlers get the realiii-sdk client, lazily connected (nothing dials
the engine until first use, so code that never touches
iiipays nothing). Node runtimesget the SDK planted from a bundle embedded in the binary — no registry, works offline;
Python runtimes
pip install iii-sdkat creation, degrading with a clear first-use errorif PyPI is unreachable. Two semantics worth knowing: SDK-side
registerFunctionregistrations are ephemeral (they die with the guest process — use
sandbox-code-runner::register_functionviaiii.triggerto persist), and a handler thattriggers a function on its own runtime stalls on that runtime's one-exec-at-a-time slot.
Runtimes are networked. The guest's engine link rides the sandbox gateway, so
npm install/pip installwork everywhere. Guest calls carry the guest's own workeridentity — the same trust model as a worker process you run yourself. That is documented
plainly in the worker README's "Identity and reach".
Console UI — purpose-built cards for the three ops, with
runtime_idredacted at everyraw display exit (a
runtime_idis a capability: it can eval into or tear down that VM).Verification
cargo fmt --checkand
cargo clippy --all-featuresclean.SANDBOX_CODE_RUNNER_E2E=1, ~10s): one-shot/keep/reuse,register + bus invocation, teardown, guest
iii.triggerin both languages (the Python pathperforms a real
pip installin the VM), ephemeral-vs-persistent registration, and anerror-UX guard.
byte-for-byte.
Naming
The worker was
code-runnerwith a::evalfunction until this branch; the rename tosandbox-code-runner/::runis in here as five reviewed commits. Guest filenames wererealigned so each matches the function it serves — the handler runner is
invoke.{mjs,py},the run wrapper is
run.{mjs,py}. Note for future refactors:RUN_MJS/RUN_PYpreviously named the handler runner and now name the wrapper, so a naive find-and-replace
across those constants can cross the two.
Deliberately not renamed: the
CodeRunner*/CODE_RUNNER_*Rust identifiers (~110occurrences, crate is
publish = false, zero blast radius) and bare English "eval" in testidentifiers. The TypeScript twin was renamed, so those layers disagree on purpose.
Also in this PR
feat(console): redact worker-declared secrets at every raw display exit— the sharedredaction contract this worker's cards depend on. It is a separate first commit and is
useful independently (node-engine's
runtime_idneeds it too).Heads-up
.github/workflows/create-tag.ymlandrelease.yml— both arethe worker-list / tag-pattern edits this branch made, against entries main added since.
Mechanical to resolve; say the word and I'll merge main in and re-run the suite and e2e.
sandbox::fs::writes persist into the shared rootfs cache(
~/.iii/cache/docker.io-iiidev-*/) — files planted in one VM showed up in later freshVMs, and a stale file broke an import until the cache was purged by hand. That is
cross-run image pollution in the daemon, not in this worker; worth its own issue.
Summary by CodeRabbit
New Features
Bug Fixes