Skip to content

fix(cloud-agent-next): correct containers status, terminal and cgroups - #6698

Merged
eshurakov merged 3 commits into
mainfrom
eshurakov/proud-anchor
Sep 28, 2026
Merged

eshurakov merged 3 commits into
mainfrom
eshurakov/proud-anchor

Conversation

@eshurakov

@eshurakov eshurakov commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes three defects reported for the new Cloudflare Containers control-plane runtime (provider cloudflare-containers, Durable Object SandboxContainers).

  1. Sandbox status popover showed the wrong type and capacity. Containers sessions were classified as isolated-small because the runtime metadata derives the type from the session id, so a 4 vCPU / 12 GiB container rendered as Sandbox type: Small, Capacity: 2 vCPU / 6 GiB. The metadata type now carries containers-standard-3 / containers-standard-4 (set where the instance is known) and is overlaid on read from the persisted instance, so the popover shows the real instance under one cross-provider memory ladder (Small ≤ 6 GiB, Medium 8 GiB, Large 12 GiB), and the Provider row now reads Cloudflare Containers rather than Cloudflare. No metadata migration, and getSandboxStatus does not write.
  2. Containers terminals were denied with billing_runtime_mismatch. validateContainersTerminalBillingRuntime had copied the legacy containment rejection, where containment selects a distinct container class. Containers use one SandboxContainers class and enforce containment with the outbound intercept proxy, so the default (containment-enabled) session was rejected before the real checks ran. Only that branch is removed; class, service, sku, logical instance and Durable Object checks remain.
  3. kilo-tools had no cgroup memory cap. The containers cgroup namespace root is populated by PID 1 and the control wrapper, and cgroup v2 refuses to enable controllers on a populated cgroup, so findUsableParent returned not_delegated and no kilo-workloads parent was created. The wrapper now evacuates the root into kilo-runtime (only after ordinary self-enable fails, and only when membership is the mount root) and then enables the controllers; the containers adapter supplies CONTROL_WORKLOAD_LIMIT_MB from the instance memory size. End-to-end verification then showed the cap was still inert because managed placement fell back to unmanaged: releaseGate read Bun's private _handle.fd, which is absent on the gate Socket. It now releases the gate with gate.end('start\n') — the standard writable-stream release.

Verification

Manual, local (dev stack on port offset 700; the Cloudflare Containers image was rebuilt from this branch's source, and the wrapper bundle inside it was confirmed to match).

  • Popover, read from the rendered DOM on the live stack: containers standard-4 shows Cloudflare Containers / Large / 4 vCPU / 12 GiB; standard-3 shows Cloudflare Containers / Medium / 2 vCPU / 8 GiB; legacy cloudflare still shows Cloudflare / Small / 2 vCPU / 6 GiB. The type and capacity rows are now distinct strings for containers (they previously printed the same capacity twice).
  • Picker: the new-session menu resolves the containers options from the billing service rather than the label — trigger Kilo · Cloudflare Containers · 2 vCPU / 8 GiB, menu entry 2 vCPU / 8 GiB · Medium.
  • Terminal: a containers standard-4 terminal connects and streams a command; the worker log has 0 billing_runtime_mismatch.
  • cgroup, read inside the running container while a Kilo-started tool process ran: server/cgroup.procs holds the kilo serve PID; tools/cgroup.procs holds a Kilo-started tool PID; the scope's cgroup.events is populated 1; tools/memory.max is 10737418240; the wrapper is 0::/kilo-runtime and outside kilo-workloads; control.workload is applied with workloadLimitSource: explicit and no workloadFailure: unavailable.
  • Automated: service and web typecheck pass; wrapper suite 1720 pass / 1 skip (one flaky collectWorktreeChanges case that passes in isolation and is unrelated); web Jest 106 pass; service unit tests plus the Workers-runtime terminal-billing integration test pass. No new lint errors.

Limitations: production Cloudflare Containers container.exec placement after delegation is not verified locally; it is a rollout gate — if a post-delegation log-tail or recovery-probe exec targets the controller-enabled namespace root and fails, this must not ship. The containment-enabled terminal branch is covered by unit tests only because local dev runs with containment disabled. tools is empty at steady state (fake-LLM file tools run in-process and Kilo-started helpers are short-lived), so tool membership was proven by polling.

Visual Changes

Before: containers sessions showed Sandbox type: Small / Capacity: 2 vCPU / 6 GiB, and the terminal showed Terminal access denied: billing_runtime_mismatch.

After: containers standard-4 After: containers standard-3
Sandbox status, containers standard-4 Sandbox status, containers standard-3
After: legacy cloudflare unchanged (regression) After: terminal connected
Sandbox status, legacy cloudflare Terminal connected

Reviewer Notes

  • Items 1 and 2 are self-contained. Item 3 touches the shared owned-processes.ts releaseGate, used by both the managed and the legacy placement paths; the new release path is the standard writable-stream API, and the legacy path was re-checked via the full wrapper suite.
  • The findUsableParent evacuation only runs after ordinary self-enable fails, only for membership /, and only when the root is populated; a nested membership never evacuates, and a delegated ancestor returns before it. Failure stays not_delegated and never fails allocation.
  • Deploy the web with or before the worker: the new sandboxType values are additive, and the web client strict-parses the status snapshot, so an older web would render the whole popover as Unknown until it is deployed.
  • Naming is label-only: no stored identifier, sandboxType enum value, Durable Object metadata or capacity number changed, and no migration is needed — containers-standard-3 / containers-standard-4 remain the persisted and API values. The webhook-trigger allocation picker (TriggerForm) deliberately keeps its Dedicated Standard label: it is an allocation-policy control that contrasts with Automatic and states its capacity in its help text, not the cross-provider size picker.
  • Pre-existing (unchanged code): 5 oxlint errors in test/integration/sandbox-control.test.ts.

Fix three defects in the Cloudflare Containers control-plane runtime:

- Report the real container instance (containers-standard-3/4) in the
  sandbox status runtime metadata and overlay it on read, so the web
  Sandbox popover stops showing the legacy isolated-small type/capacity.
- Drop the copied containment rejection in the containers terminal billing
  validator; containment does not select a containers class.
- Delegate the populated containers cgroup root so kilo-workloads can be
  created, supply CONTROL_WORKLOAD_LIMIT_MB from the instance memory, and
  release the gated child with the stream API so managed placement works.

Verified locally end to end (popover, terminal, live cgroup membership).
@kilo-code-bot

kilo-code-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental re-review of commit c163e470c (refactor(cloud-agent-next): label sandbox tiers with a memory ladder) found no new defects: the reworked web picker/status label maps, the widened formatContainerCapacity signature, and the added Cloudflare Containers provider label are display-only and covered by updated unit, e2e, and capacity-parity tests.

Files Reviewed (12 files)
  • apps/web/src/components/cloud-agent-next/sandbox-selection.ts
  • apps/web/src/components/cloud-agent-next/sandbox-selection.test.ts
  • apps/web/src/components/cloud-agent-next/sandbox-status.ts
  • apps/web/src/components/cloud-agent-next/sandbox-status.test.ts
  • apps/web/src/lib/cloudflare/container-capacity.ts
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts
  • apps/web/tests/e2e/cloud-agent-sandbox-status.spec.ts
  • services/cloud-agent-next/src/container-capacity-parity.test.ts
  • services/cloud-agent-next/src/sandbox-control/lifecycle.test.ts
  • services/cloud-agent-next/src/shared/sandbox-status.ts
  • services/cloud-agent-next/src/shared/sandbox-status.test.ts
  • services/cloud-agent-next/src/persistence/SandboxControl.ts (targeted verification of the active inline comment)

Notes

  • instanceLabels / sandboxTypes now resolve the same memory ladder (Small <= 6 GiB, Medium 8 GiB, Large 12 GiB) for the picker and the status popover; every selectable destination still produces a distinct capacity string, and the superseded Standard / Dedicated Standard expectations were updated in the unit, schema, and e2e tests.
  • formatContainerCapacity was widened to { vcpu; memoryBytes } and formatVercelCapacity reuses the existing worker-utils Vercel resource table (memory is MiB), so the rendered GiB values are unchanged; the parity test now checks the web source maps standard-3 / standard-4 to usageServiceForSandboxClass(...) instead of a hard-coded capacity string, which still matches containerCapacityForService.
  • The Cloudflare Containers provider label is additive in the shared SandboxProviderLabelSchema; no code switches on the label string, but the existing deploy ordering note (ship web with or before the worker) still applies because the client strict-parses the snapshot.
  • The one active inline comment (services/cloud-agent-next/src/persistence/SandboxControl.ts:3114) is on a file this commit does not touch and is not re-raised or duplicated here.
Previous Review Summaries (2 snapshots, latest commit 7b144a5)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7b144a5)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental re-review of commit 7b144a53e (style(cloud-agent-next): apply oxfmt to container parity and durable-state tests), which only reflows whitespace/line-wrapping in two test files with no semantic change; no new findings, so the prior clean assessment of the three Cloudflare Containers control-plane fixes stands.

Files Reviewed (20 files)
  • .specs/cloud-agent-session.md
  • apps/web/src/components/cloud-agent-next/sandbox-status.ts
  • apps/web/src/components/cloud-agent-next/sandbox-status.test.ts
  • apps/web/src/lib/cloudflare/container-capacity.ts
  • services/cloud-agent-next/src/container-capacity-parity.test.ts
  • services/cloud-agent-next/src/persistence/SandboxControl.ts
  • services/cloud-agent-next/src/sandbox-control/cloudflare-containers-provider.ts
  • services/cloud-agent-next/src/sandbox-control/cloudflare-containers-provider.test.ts
  • services/cloud-agent-next/src/sandbox-control/durable-state.ts
  • services/cloud-agent-next/src/sandbox-control/durable-state.test.ts
  • services/cloud-agent-next/src/sandbox-control/lifecycle.test.ts
  • services/cloud-agent-next/src/sandbox-control/terminal-billing.ts
  • services/cloud-agent-next/src/sandbox-control/terminal-billing.test.ts
  • services/cloud-agent-next/src/shared/sandbox-status.ts
  • services/cloud-agent-next/src/shared/sandbox-status.test.ts
  • services/cloud-agent-next/test/integration/sandbox-control.test.ts
  • services/cloud-agent-next/wrapper/src/control/owned-processes.ts
  • services/cloud-agent-next/wrapper/src/control/owned-processes.test.ts
  • services/cloud-agent-next/wrapper/src/control/workload-cgroup.ts
  • services/cloud-agent-next/wrapper/src/control/workload-cgroup.test.ts

Notes

  • The incremental range 9e45b0918..7b144a53e contains one formatting-only commit affecting container-capacity-parity.test.ts and durable-state.test.ts; both changes reflow existing assertions/it.each calls and are behavior-preserving.
  • No new or unresolved Code Review Findings remain, so no fix link is included.

Previous review (commit 9e45b09)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the three Cloudflare Containers control-plane fixes (status runtime-type projection, containers terminal billing validator, and cgroup delegation plus gated-child release); the changed lines are internally consistent and I found no high-confidence defects.

Files Reviewed (20 files)
  • .specs/cloud-agent-session.md
  • apps/web/src/components/cloud-agent-next/sandbox-status.ts
  • apps/web/src/components/cloud-agent-next/sandbox-status.test.ts
  • apps/web/src/lib/cloudflare/container-capacity.ts
  • services/cloud-agent-next/src/container-capacity-parity.test.ts
  • services/cloud-agent-next/src/persistence/SandboxControl.ts
  • services/cloud-agent-next/src/sandbox-control/cloudflare-containers-provider.ts
  • services/cloud-agent-next/src/sandbox-control/cloudflare-containers-provider.test.ts
  • services/cloud-agent-next/src/sandbox-control/durable-state.ts
  • services/cloud-agent-next/src/sandbox-control/durable-state.test.ts
  • services/cloud-agent-next/src/sandbox-control/lifecycle.test.ts
  • services/cloud-agent-next/src/sandbox-control/terminal-billing.ts
  • services/cloud-agent-next/src/sandbox-control/terminal-billing.test.ts
  • services/cloud-agent-next/src/shared/sandbox-status.ts
  • services/cloud-agent-next/src/shared/sandbox-status.test.ts
  • services/cloud-agent-next/test/integration/sandbox-control.test.ts
  • services/cloud-agent-next/wrapper/src/control/owned-processes.ts
  • services/cloud-agent-next/wrapper/src/control/owned-processes.test.ts
  • services/cloud-agent-next/wrapper/src/control/workload-cgroup.ts
  • services/cloud-agent-next/wrapper/src/control/workload-cgroup.test.ts

Notes

  • getSandboxStatus overlays runtime.sandboxType from persisted provider_configuration on read without writing; the new sandboxType enum members are shared by the worker and web via services/cloud-agent-next/src/shared/sandbox-status.ts, so there is no producer/consumer union drift.
  • Removing the providerRef.containment rejection in validateContainersTerminalBillingRuntime keeps the class/service/SKU/logical-instance/Durable-Object binding checks, so it is not an authorization regression.
  • launch() derives CONTROL_WORKLOAD_LIMIT_MB from containersBillingIdentity(instance); deps.instance is only ever populated from the restricted z.enum(CLOUDFLARE_CONTAINERS_INSTANCES) provider config, so the call cannot hit an unsupported size in shipped paths.
  • The reworked releaseGate error listener absorbs an asynchronous EPIPE, but an EPIPE implies the gated shell already closed fd 3 (it only does so after reading the start token), so containment is already established and no live child can hang; the legacy path was re-checked in the wrapper suite.

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

// `provider_configuration`, so project its type on read; a stored
// `isolated-small` from the id classification never reaches the client.
let projectedRuntime = runtime;
const configuration = sandboxProviderConfigurationSchema.safeParse(providerConfiguration);

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.

bot: Status projection misses legacy provider-kind-only containers records

Suggested fix: Resolve the effective provider configuration using the same legacy provider_kind fallback used by readProviderConfiguration before deciding whether to overlay the containers type. If the instance cannot be recovered, avoid exposing the stale ID-derived type as though it were authoritative.

Name Cloudflare, Cloudflare Containers and Vercel sandboxes by one memory ladder (Small <= 6 GiB, Medium 8 GiB, Large 12 GiB), so the status popover and the new-session picker use the same words across providers and the containers type row stops repeating the capacity row. Label only: stored identifiers, the sandboxType enum, runtime metadata and capacity numbers are unchanged.

- popover: containers standard-3/4 render Medium/Large; provider reads Cloudflare Containers
- shared tenancy reads Large . Shared so it stays distinct from the dedicated Large at the same capacity
- picker keeps vCPU/memory numbers by resolving capacity from the billing service rather than the label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants