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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/sandbox-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ An allocation moves to `cleanup_pending` when its Session is deleted or archived

A Provider declares `DeploymentPolicy.Network`: `Restrict` means it can refuse public addresses and allow named public domains, and Core admits `disabled` and `restricted` Environments only then; `PrivateDomains` means it can allow the private addresses that named hosts resolve to. Core validates the deployment's [network cap](../contracts/agents-api/sandbox-deployment.md#network) against the declaration when the selection is saved and never substitutes another behavior.

Core sends the same `Egress` in `Bootstrap` and in every `ResumeRequest`, derived from the allocation's deployment generation, its Session's frozen Environment `network` policy and its frozen model provider. `disabled` leaves only `Endpoints`; `restricted` clears `Public` and lists the allowed domains in `PrivateDomains` when the cap names them and in `Domains` otherwise ([policy](../contracts/agents-api/environments.md#restricted-network)):
Core sends the same `Egress` in `Bootstrap` and in every `ResumeRequest`, derived from the allocation's deployment generation, the Environment's frozen `network` policy and, for a Session-owned Environment, its Session's model provider. `disabled` leaves only `Endpoints`; `restricted` clears `Public` and lists the allowed domains in `PrivateDomains` when the cap names them and in `Domains` otherwise ([policy](../contracts/agents-api/environments.md#restricted-network)):

- `Public`: public addresses are allowed.
- `PrivateDomains`: exact host names, or `*.example.com` for its subdomains but not `example.com` itself, whose resolved addresses, private ones included, are allowed.
Expand Down
4 changes: 2 additions & 2 deletions docs/zh/sandbox-provider.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "沙箱提供商协议"
source: docs/sandbox-provider.md
source_hash: 8d1fe4e681774523fb1544201f2ef950ac29ed2a8d055c5bf0b63855e30a24c7
source_hash: 9884514f37032ccc7c97fe0a0a0ddced652e55dfe59d98a55a72ff4e0b75a810
---

沙箱提供商协议连接 Core 与为 Core 管理的(`openai_hosted`)Environment 提供计算资源的 adapter,并由该 adapter 运行启动 Runtime daemon 的有界引导流程。本页面向 adapter 作者:说明提供商必须实现什么,以及可以依赖 Core 做什么。microsandbox、E2B 和 OpenSandbox 实现了该协议。
Expand Down Expand Up @@ -197,7 +197,7 @@ stateDiagram-v2

Provider 声明 `DeploymentPolicy.Network`:`Restrict` 表示能拒绝公开地址并允许具名公开域名,Core 仅在此时准入 `disabled` 和 `restricted` Environment;`PrivateDomains` 表示能允许具名主机解析出的私有地址。保存选择时,Core 会依据该声明校验部署的[网络上限](../../contracts/agents-api/zh/sandbox-deployment.md#network),绝不替换为其他行为。

Core 在 `Bootstrap` 和每个 `ResumeRequest` 中发送同一个 `Egress`,它由分配所属的部署代次和 Session 冻结的 Environment `network` 策略和冻结的模型提供商推导而来。`disabled` 只留下 `Endpoints`;`restricted` 清除 `Public`,上限列出的域名放入 `PrivateDomains`,其余放入 `Domains`([策略](../../contracts/agents-api/zh/environments.md#restricted-network)):
Core 在 `Bootstrap` 和每个 `ResumeRequest` 中发送同一个 `Egress`,它由分配所属的部署代次和 Environment 冻结的 `network` 策略,以及(对 Session 拥有的 Environment)其 Session 的模型提供商推导而来。`disabled` 只留下 `Endpoints`;`restricted` 清除 `Public`,上限列出的域名放入 `PrivateDomains`,其余放入 `Domains`([策略](../../contracts/agents-api/zh/environments.md#restricted-network)):

- `Public`:允许公开地址。
- `PrivateDomains`:精确主机名,或 `*.example.com`(覆盖其子域名,不含 `example.com` 本身),其解析出的地址(包括私有地址)都被允许。
Expand Down
4 changes: 2 additions & 2 deletions services/core/cmd/server/managed_generations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ q=json.load(sys.stdin)
with (pathlib.Path(q['Config']['StateDir'])/'requests').open('a') as f: f.write(json.dumps(q)+'\n')
info=dict(q['Reference'],State='running',ProviderID='owned',CreateSettled=True)
if q['Operation']=='kill': info['State']='absent'
print(json.dumps({'Version':3,'Info':info}))
print(json.dumps({'Version':4,'Info':info}))
`
if err := os.WriteFile(helper, []byte(script), 0700); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -132,7 +132,7 @@ code = ''
if k == 'revoked': code = 'unauthorized'
elif k == 'unconfirmed': code = 'unconfirmed'
elif not (k.startswith('team-a') and template in ('owned-a', 'new-a') or k == 'team-b' and template == 'public-b'): code = 'team_mismatch'
result = {'Version': 3, 'ErrorCode': code}
result = {'Version': 4, 'ErrorCode': code}
if not code:
result['DeploymentValid'] = True
if q['Operation'] == 'validate_deployment':
Expand Down
10 changes: 5 additions & 5 deletions services/core/cmd/server/managed_setup_preflight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (

func TestE2BRejectedSpecificationHasSafeActionableDiagnostic(t *testing.T) {
helper := filepath.Join(t.TempDir(), "provider")
if err := os.WriteFile(helper, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":3,\"ErrorCode\":\"invalid\"}'\n"), 0700); err != nil {
if err := os.WriteFile(helper, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":4,\"ErrorCode\":\"invalid\"}'\n"), 0700); err != nil {
t.Fatal(err)
}
state := filepath.Join(t.TempDir(), "e2b")
Expand Down Expand Up @@ -49,12 +49,12 @@ op = q['Operation']
with (pathlib.Path(q['Config']['StateDir']) / 'operations').open('a') as log:
log.write(op + '\n')
if op == 'validate_deployment':
print(json.dumps({'Version': 3, 'ErrorCode': 'invalid'}))
print(json.dumps({'Version': 4, 'ErrorCode': 'invalid'}))
else:
info = dict(q['Reference'], ProviderID='owned-compute', State='stopped', CreateSettled=True)
if op == 'kill':
info.update(ProviderID='', State='absent')
print(json.dumps({'Version': 3, 'Info': info}))
print(json.dumps({'Version': 4, 'Info': info}))
`
if err := os.WriteFile(helper, []byte(script), 0700); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestE2BCandidateAdoptsTemplateBuildForOmittedResources(t *testing.T) {
}
helper := filepath.Join(t.TempDir(), "provider")
requests := filepath.Join(state, "requests")
script := "#!/bin/sh\ncat >>" + requests + "\necho >>" + requests + "\nprintf '%s' '{\"Version\":3,\"ErrorCode\":\"\",\"DeploymentValid\":true,\"TemplateBuild\":{\"Status\":\"ready\",\"CPUs\":4,\"MemoryMiB\":4096,\"RootDiskMiB\":24063}}'\n"
script := "#!/bin/sh\ncat >>" + requests + "\necho >>" + requests + "\nprintf '%s' '{\"Version\":4,\"ErrorCode\":\"\",\"DeploymentValid\":true,\"TemplateBuild\":{\"Status\":\"ready\",\"CPUs\":4,\"MemoryMiB\":4096,\"RootDiskMiB\":24063}}'\n"
if err := os.WriteFile(helper, []byte(script), 0700); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestE2BCandidateAdoptsTemplateBuildForOmittedResources(t *testing.T) {

func TestInitialE2BPublicTemplateOutsideTeamIsRejected(t *testing.T) {
helper := filepath.Join(t.TempDir(), "provider")
if err := os.WriteFile(helper, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":3,\"ErrorCode\":\"team_mismatch\"}'\n"), 0700); err != nil {
if err := os.WriteFile(helper, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":4,\"ErrorCode\":\"team_mismatch\"}'\n"), 0700); err != nil {
t.Fatal(err)
}
state := filepath.Join(t.TempDir(), "e2b")
Expand Down
2 changes: 1 addition & 1 deletion services/core/deploy/e2b/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ python -m venv "$HOME/.oac/build/e2b-sdk"
--output "$HOME/.oac/build/e2b-template.json"
```

[`build-template.py`](build-template.py) requires a `sha256:` image ID, a Linux amd64 image and the Runtime layout. It copies the image's `/usr/local/bin`, `/opt` and, when present, `/usr/local/codex-resources` and `/etc/codex`, together with its `HOME` and `OAC_*` environment, onto a digest-pinned `node:22.23.1-bookworm-slim` base with `ca-certificates`, `bash`, `git`, `python3`, `python3-pip`, `ripgrep` and `util-linux`. Rebuild a template whenever the Runtime bootstrap version changes: Create refuses a template whose contract names another version. It installs [`init.py`](init.py) and [`managed_init.py`](managed_init.py) read-only under `/opt/oac-e2b`, runs the daemon as UID/GID 1000 (`runtime`, home `/home/runtime`) and builds with 2 vCPUs and 2048 MiB of memory unless `--memory-mb` gives another size. Only the `usr`, `usr/local` and `etc` archive ancestors it creates get traversable modes; Runtime file modes, private build contexts, key inputs and the output's umask stay unchanged.
[`build-template.py`](build-template.py) requires a `sha256:` image ID, a Linux amd64 image and the Runtime layout. It copies the image's `/usr/local/bin`, `/opt` and, when present, `/usr/local/codex-resources` and `/etc/codex`, together with its `HOME` and `OAC_*` environment, onto a digest-pinned `node:22.23.1-bookworm-slim` base with `ca-certificates`, `bash`, `git`, `python3`, `python3-pip`, `ripgrep` and `util-linux`. Rebuild a template whenever the E2B helper protocol or the Runtime bootstrap version changes: Create refuses a template whose contract names other versions. It installs [`init.py`](init.py) and [`managed_init.py`](managed_init.py) read-only under `/opt/oac-e2b`, runs the daemon as UID/GID 1000 (`runtime`, home `/home/runtime`) and builds with 2 vCPUs and 2048 MiB of memory unless `--memory-mb` gives another size. Only the `usr`, `usr/local` and `etc` archive ancestors it creates get traversable modes; Runtime file modes, private build contexts, key inputs and the output's umask stay unchanged.

The output file records `template` (the immutable `templateID:build_UUID`), the source `image`, the packaged `runtime_sha256` and the `base`. Use that exact `template` value. The template must qualify every Harness its image advertises. Install system dependencies into the image; the daemon runs as UID/GID 1000. No E2B account key, executor key or model credential belongs in a build, template environment, metadata, command argument or log.

Expand Down
8 changes: 4 additions & 4 deletions services/core/deploy/e2b/helper_contract_generated.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Code generated by contractgen; DO NOT EDIT.
"""Adapter-private wire declarations. No SDK or repository dependency."""
ERROR_CODES = ["","invalid","ownership","exists","not_found","command_unconfirmed","unconfirmed","template_invalid","team_mismatch","unauthorized","checkpoint_unusable"]
MANAGED_BOOTSTRAP_FIELDS = ["Workspace","TenantID","EnvironmentID","AllocationID","SessionID","DeviceID","InstallationID","RuntimeBootstrap"]
MANAGED_BOOTSTRAP_REQUIRED_FIELDS = ["TenantID","EnvironmentID","AllocationID","SessionID","DeviceID","InstallationID","RuntimeBootstrap"]
MANAGED_IDENTITY_FIELDS = ["TenantID","EnvironmentID","AllocationID","SessionID","DeviceID","InstallationID"]
MANAGED_BOOTSTRAP_FIELDS = ["Workspace","TenantID","EnvironmentID","AllocationID","DeviceID","InstallationID","RuntimeBootstrap"]
MANAGED_BOOTSTRAP_REQUIRED_FIELDS = ["TenantID","EnvironmentID","AllocationID","DeviceID","InstallationID","RuntimeBootstrap"]
MANAGED_IDENTITY_FIELDS = ["TenantID","EnvironmentID","AllocationID","DeviceID","InstallationID"]
MAX_COMMAND_INPUT = 52428832
MAX_CREDENTIAL_REFERENCES = 32
MAX_OUTPUT = 1048576
MAX_REQUEST = 75497472
MAX_RESPONSE = 16777216
OPERATIONS = ["create","inspect","renew","kill","command","validate_deployment","observe","list_templates","list_builds","verify_credential","compute","renew_compute","suspend","resume","kill_compute","delete_snapshot","command_compute","resume_compute"]
PROTOCOL_VERSION = 3
PROTOCOL_VERSION = 4
REFERENCE_FIELDS = ["TenantID","EnvironmentID","AllocationID"]
REQUEST_FIELDS = ["Version","Operation","Config","Reference","References","Bootstrap","RuntimeBootstrap","Command","Compute","Suspend","Resume","Snapshot","Deadline"]
RESPONSE_FIELDS = ["Version","Info","Command","State","ErrorCode","DeploymentValid","TemplateBuild","Templates","Builds","Observation"]
Expand Down
2 changes: 1 addition & 1 deletion services/core/deploy/e2b/managed_init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def payload():
value = {key: str(uuid4()) for key in ['InstallationID', 'TenantID', 'EnvironmentID',
'AllocationID', 'SessionID', 'DeviceID']}
'AllocationID', 'DeviceID']}
return dict(value, RuntimeBootstrap={'version': 4, 'core_url': 'https://core.example/api/v1',
'device_id': value['DeviceID'], 'credential': 'private-managed-token',
'environment_id': value['EnvironmentID'], 'workspace': '/environment/workspace', 'suspend_control': True})
Expand Down
2 changes: 1 addition & 1 deletion services/core/internal/execution/runtime_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ func (r *runtimeLifecycle) provision(ctx context.Context, tenant, environment, p
}
started := time.Now()
info, err := provider.Create(ctx, sandbox.Bootstrap{
Reference: runtimeReference(owner), SessionID: owner.SessionID, DeviceID: owner.DeviceID,
Reference: runtimeReference(owner), DeviceID: owner.DeviceID,
Workspace: workspace, CoreURL: r.config.CoreURL, Credential: token,
SuspendControl: sandbox.SupportsCheckpoint(provider.ProviderOperations()),
Egress: egress,
Expand Down
4 changes: 2 additions & 2 deletions services/core/internal/sandbox/e2b/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func (f contractCaller) Call(ctx context.Context, q Request) (Response, error) {
func TestProviderContract(t *testing.T) {
contracttest.RunFailures(t, func(t *testing.T, s contracttest.Scenario, cancel context.CancelFunc) contracttest.Fixture {
p, _, r := fixture(t)
b := sandbox.Bootstrap{Reference: r, SessionID: uuid.NewString(), DeviceID: uuid.NewString(), CoreURL: "https://core.example/api/v1", Credential: "synthetic"}
b := sandbox.Bootstrap{Reference: r, DeviceID: uuid.NewString(), CoreURL: "https://core.example/api/v1", Credential: "synthetic"}
var calls []string
p.caller = contractCaller(func(ctx context.Context, q Request) (Response, error) {
calls = append(calls, q.Operation)
Expand Down Expand Up @@ -47,7 +47,7 @@ func TestProviderContract(t *testing.T) {
func TestProviderContractObservation(t *testing.T) {
p, f, r := fixture(t)
f.response.Info = &sandbox.Info{Reference: r, ProviderID: "native-owned", State: "running", CreateSettled: true, BootstrapComplete: true}
b := sandbox.Bootstrap{Reference: r, SessionID: uuid.NewString(), DeviceID: uuid.NewString(), CoreURL: "https://core.example/api/v1", Credential: "synthetic"}
b := sandbox.Bootstrap{Reference: r, DeviceID: uuid.NewString(), CoreURL: "https://core.example/api/v1", Credential: "synthetic"}
got, err := p.Create(bounded(t), b)
contracttest.AssertObservation(t, got, err, r, "native-owned", "running")
got, err = p.GetInfo(bounded(t), r)
Expand Down
2 changes: 1 addition & 1 deletion services/core/internal/sandbox/e2b/helper_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
//go:generate go run ./internal/contractgen

// This adapter-private boundary is documented in tools/e2b-provider/README.md.
const ProtocolVersion = 3
const ProtocolVersion = 4
const MaxOutputBytes = 1024 * 1024
const MaxRequestBytes = 72 * 1024 * 1024
const MaxResponseBytes = 16 * 1024 * 1024
Expand Down
2 changes: 1 addition & 1 deletion services/core/internal/sandbox/e2b/helper_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,5 @@ func validManagedExchange(data []byte) bool {
delete(fields, "RuntimeBootstrap")
data, _ = json.Marshal(fields)
var bootstrap sandbox.Bootstrap
return json.Unmarshal(data, &bootstrap) == nil && validReference(bootstrap.Reference) && validID(bootstrap.DeviceID) && validID(bootstrap.SessionID)
return json.Unmarshal(data, &bootstrap) == nil && validReference(bootstrap.Reference) && validID(bootstrap.DeviceID)
}
2 changes: 1 addition & 1 deletion services/core/internal/sandbox/e2b/installed_paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestConfigurationDiscoveryUsesSuppliedProcessPaths(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(binary), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(binary, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":3,\"Templates\":[]}'\n"), 0700); err != nil {
if err := os.WriteFile(binary, []byte("#!/bin/sh\ncat >/dev/null\nprintf '%s' '{\"Version\":4,\"Templates\":[]}'\n"), 0700); err != nil {
t.Fatal(err)
}
input := sandbox.ConfigurationDiscoveryInput{Credential: json.RawMessage(`{"api_key":"synthetic-key"}`)}
Expand Down
10 changes: 4 additions & 6 deletions services/core/internal/sandbox/e2b/internal/contractgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,10 @@ func main() {
bootstrap = slices.DeleteFunc(bootstrap, func(s string) bool { return s == "CoreURL" || s == "Credential" || s == "SuspendControl" || s == "Egress" })
bootstrap = append(bootstrap, "InstallationID", "RuntimeBootstrap")
identity := fields(reflect.TypeFor[sandbox.Reference]())
for _, name := range []string{"SessionID", "DeviceID"} {
if !slices.Contains(bootstrap, name) {
panic("bootstrap identity field missing: " + name)
}
identity = append(identity, name)
if !slices.Contains(bootstrap, "DeviceID") {
panic("bootstrap identity field missing: DeviceID")
}
identity = append(identity, "DeviceID")
identity = append(identity, "InstallationID")
values := map[string]any{
"PROTOCOL_VERSION": e2b.ProtocolVersion, "SDK_VERSION": sdk,
Expand Down Expand Up @@ -110,7 +108,7 @@ func main() {
// envelopes and managed bootstrap inputs in both implementations.
func fixtures() []byte {
r := sandbox.Reference{TenantID: "11111111-1111-4111-8111-111111111111", EnvironmentID: "22222222-2222-4222-8222-222222222222", AllocationID: "33333333-3333-4333-8333-333333333333"}
b := sandbox.Bootstrap{Reference: r, SessionID: "44444444-4444-4444-8444-444444444444", DeviceID: "55555555-5555-4555-8555-555555555555", CoreURL: "https://core.example/api/v1", Credential: "fixture-only"}
b := sandbox.Bootstrap{Reference: r, DeviceID: "55555555-5555-4555-8555-555555555555", CoreURL: "https://core.example/api/v1", Credential: "fixture-only"}
installation := "66666666-6666-4666-8666-666666666666"
connection := b.RuntimeConnection()
q := e2b.Request{Version: e2b.ProtocolVersion, Operation: "create", Config: e2b.Config{InstallationID: installation}, Reference: r, Bootstrap: &b, RuntimeBootstrap: &connection, Deadline: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC)}
Expand Down
8 changes: 4 additions & 4 deletions services/core/internal/sandbox/e2b/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func TestProcessWireRejectsUnknownAndTrailingOutput(t *testing.T) {
for _, output := range []string{`{"Version":3,"Secret":"private"}`, `{"Version":3} {"Version":3}`} {
for _, output := range []string{`{"Version":4,"Secret":"private"}`, `{"Version":4} {"Version":4}`} {
root := t.TempDir()
binary := filepath.Join(root, "helper")
if err := os.WriteFile(binary, []byte("#!/bin/sh\nprintf '%s' '"+output+"'\n"), 0700); err != nil {
Expand All @@ -29,7 +29,7 @@ func TestTimeoutDoesNotTerminateOwnedHelper(t *testing.T) {
marker := filepath.Join(root, "settled")
// The private test path contains no shell syntax; the real adapter never puts
// credentials or request contents in argv or inherited environment.
script := "#!/bin/sh\nsleep 0.15\ntouch '" + marker + "'\nprintf '%s' '{\"Version\":3}'\n"
script := "#!/bin/sh\nsleep 0.15\ntouch '" + marker + "'\nprintf '%s' '{\"Version\":4}'\n"
if err := os.WriteFile(binary, []byte(script), 0700); err != nil {
t.Fatal(err)
}
Expand All @@ -50,7 +50,7 @@ func TestTimeoutDoesNotTerminateOwnedHelper(t *testing.T) {
func TestEnvironmentDropsProviderSelectorsAndCredentials(t *testing.T) {
root := t.TempDir()
binary := filepath.Join(root, "helper")
script := "#!/bin/sh\nif test -n \"${E2B_API_KEY:-}${E2B_API_URL:-}${PRIVATE_MODEL_KEY:-}\"; then exit 1; fi\nprintf '%s' '{\"Version\":3}'\n"
script := "#!/bin/sh\nif test -n \"${E2B_API_KEY:-}${E2B_API_URL:-}${PRIVATE_MODEL_KEY:-}\"; then exit 1; fi\nprintf '%s' '{\"Version\":4}'\n"
if err := os.WriteFile(binary, []byte(script), 0700); err != nil {
t.Fatal(err)
}
Expand All @@ -67,7 +67,7 @@ func TestCredentialFenceWaitsForActualHelperExitAfterCancellation(t *testing.T)
binary := filepath.Join(root, "helper")
marker := filepath.Join(root, "started")
finish := filepath.Join(root, "finish")
script := "#!/bin/sh\ntouch '" + marker + "'\nwhile ! test -f '" + finish + "'; do sleep 0.01; done\nprintf '%s' '{\"Version\":3}'\n"
script := "#!/bin/sh\ntouch '" + marker + "'\nwhile ! test -f '" + finish + "'; do sleep 0.01; done\nprintf '%s' '{\"Version\":4}'\n"
if err := os.WriteFile(binary, []byte(script), 0700); err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion services/core/internal/sandbox/e2b/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ func (p *Provider) Create(ctx context.Context, b sandbox.Bootstrap) (sandbox.Inf
if b.Workspace != nil {
return sandbox.Info{Reference: b.Reference}, &providercontract.UnsupportedError{Operation: "Create", Reason: "external_workspace_unsupported"}
}
if !validReference(b.Reference) || !validID(b.SessionID) || !validID(b.DeviceID) || b.RuntimeConnection().Validate() != nil {
if !validReference(b.Reference) || !validID(b.DeviceID) || b.RuntimeConnection().Validate() != nil {
info := sandbox.Info{Reference: b.Reference}
if validReference(b.Reference) {
info.State, info.CreateSettled = "absent", true
Expand Down
Loading
Loading