Skip to content

fix(cloud-agent-next): release an isolated sandbox when its sole session is deleted - #6658

Merged
eshurakov merged 3 commits into
mainfrom
eshurakov/polished-delta
Sep 24, 2026
Merged

eshurakov merged 3 commits into
mainfrom
eshurakov/polished-delta

Conversation

@eshurakov

Copy link
Copy Markdown
Contributor

Summary

Deleting a workspace session left its Cloudflare sandbox allocation running until the Durable
Object idle sweep (10 minutes), so a workload that creates and deletes sessions never released its
containers. A manual Cloud Agent E2E tests run hit the e2e Worker's container cap because of this:
512 There is no container instance that can be provided to this Durable Object and 11
Maximum number of running container instances exceeded errors, with exactly 20 containers started.

SandboxControl.forgetSessionReference now stops the allocation when the deleted session was the
sandbox's sole owner:

  • session-ingest gains retireWorktreeIfSoleMember. It runs under the same worktree row lock as
    registerCloudAgentWorktree, so a sibling chat either commits first (visible, shared) or loses
    registration after the retirement marker (WORKTREE_DELETING). For a sole member it writes the
    full worktree deletion state through the extracted beginWorktreeDeletionOn, so a later public
    deleteWorktree can still parse the manifest and resume cleanup.
  • SandboxControl.forgetSessionReference removes the tombstone, then retires and stops via a shared
    stopFor(reason). It stays a no-op for shared and legacy-shared sandbox ids, when another route
    or reference remains, when an exclusive deletion fence is set, and for creating/unknown
    allocations. Only state.kind === 'stopped' is reported as confirmed.
  • SandboxSession.deleteSession forwards the worktree, user and organization metadata; it does not
    decide.

Verification

Manually exercised against the local dev stack (real Worker + Durable Object + sandbox + wrapper),
not only unit tests:

  • cold-hot echo:hi completed twice with real fake-LLM dispatches.
  • After deleteSession the sandbox container appeared and then disappeared
    (docker ps polled independently), with the product chain
    allocated.healthy → stopping.destroying (reason: session_deleted) → stopped (destroy_confirmed)
    and session_deleted_stop result: 'stopped'. The container lifetime was ~15s, so the 5-minute
    idle sweep is excluded as the cause.

Automated: session-ingest worktree-deletion 43 passed; cloud-agent-next unit 245 passed;
integration (worktree-deletion, sandbox-control, deletion-lifecycle) 369 passed; typecheck and
oxlint clean.

Visual Changes

N/A

Reviewer Notes

  • Deployment order matters: deploy session-ingest before cloud-agent-next. If the caller is
    ahead, the missing RPC fails closed (no stop) rather than guessing.
  • A session whose worktree still has another member is deliberately not stopped, so
    worktree-multi-chat keeps one container until the idle sweep. Sandbox-wide exclusivity for
    shared (org-/usr-) sandbox ids is out of scope.
  • The stop is attempted only for an allocated allocation. A delete that races a sandbox boot can
    still launch; the first heartbeat with an empty route table arms the existing idle sweep.
  • retention-expired deletion and the legacy cloud-agent-sandbox.ts path are unchanged.

Optimize failure handling for the user's experience: do not fail on transient
conditions, fail fast with a specific reason when progress is impossible, never
hang silently, and resume promptly when input arrives.
…ion is deleted

A session deletion left an isolated sandbox allocation running until the idle sweep, so an E2E suite that creates and deletes sessions never released its containers. forgetSessionReference now stops the allocation when the deleted session was the sandbox's sole owner.

- session-ingest adds retireWorktreeIfSoleMember, which under the worktree row lock decides exclusive/shared/unresolved and, for a sole member, writes the full worktree deletion state through the extracted beginWorktreeDeletionOn so a later deleteWorktree resumes it.

- SandboxControl.forgetSessionReference removes the tombstone, then retires and stops via the shared stopFor helper. It stays a no-op for shared and legacy-shared sandbox ids, when another reference or route remains, when an exclusive deletion fence is set, and for creating/unknown allocations.

- SandboxSession.deleteSession forwards the worktree, user and organization metadata; it does not decide.
@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commit fa267edf28 resolves both prior findings. exactSoleMember now counts only entries with a non-null cloudAgentSessionId, so subagent children no longer block retirement, and retireWorktreeIfSoleMember re-checks the committed manifest before returning exclusive, so a concurrently committed root owner cannot authorize a sandbox stop. No new issues were found in the incremental diff.

Files Reviewed (11 files)
  • AGENTS.md
  • packages/session-ingest-contracts/src/rpc-contract.ts
  • services/cloud-agent-next/src/persistence/SandboxControl.ts
  • services/cloud-agent-next/src/sandbox-control/lifecycle.test.ts
  • services/cloud-agent-next/src/sandbox-session/SandboxSession.ts
  • services/cloud-agent-next/src/sandbox-session/control-rpc.ts
  • services/cloud-agent-next/src/telemetry/control-plane-failure-fence.test.ts
  • services/cloud-agent-next/test/integration/worktree-deletion.test.ts
  • services/session-ingest/src/services/worktree-deletion.test.ts - re-reviewed, prior findings fixed
  • services/session-ingest/src/services/worktree-deletion.ts - re-reviewed, prior findings fixed
  • services/session-ingest/src/session-ingest-rpc.ts
Previous Review Summary (commit 6480d7a)

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

Previous review (commit 6480d7a)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The new sole-member retirement can stop a sandbox whose worktree has a concurrently-created sibling session, because the exclusive/shared decision is read without lockRoots and can disagree with the manifest it writes.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
services/session-ingest/src/services/worktree-deletion.ts 365 retireWorktreeIfSoleMember decides exclusive from an unlocked discoverMembers read, but the manifest is written by a second locked read; a concurrently created child session can commit in between, so the committed manifest has two members while the caller still stops the sandbox.

SUGGESTION

File Line Issue
services/session-ingest/src/services/worktree-deletion.ts 331 exactSoleMember treats null-cloudAgentSessionId child rows as other members, so a root session with subagent children is never retired and the sandbox leak persists for those sessions.
Files Reviewed (11 files)
  • AGENTS.md - no issues
  • packages/session-ingest-contracts/src/rpc-contract.ts - no issues
  • services/cloud-agent-next/src/persistence/SandboxControl.ts - no issues
  • services/cloud-agent-next/src/sandbox-control/lifecycle.test.ts - no issues
  • services/cloud-agent-next/src/sandbox-session/SandboxSession.ts - no issues
  • services/cloud-agent-next/src/sandbox-session/control-rpc.ts - no issues
  • services/cloud-agent-next/src/telemetry/control-plane-failure-fence.test.ts - no issues
  • services/cloud-agent-next/test/integration/worktree-deletion.test.ts - no issues
  • services/session-ingest/src/services/worktree-deletion.test.ts - no issues
  • services/session-ingest/src/services/worktree-deletion.ts - 2 issues
  • services/session-ingest/src/session-ingest-rpc.ts - no issues

No memory leaks found: the new RPC path uses withTimeout, which clears its timer in finally, and no listeners, alarms, or unbounded collections are added. metadata.identity.sessionId is confirmed to be the workspace_<uuid> cloud-agent session id, so the new strict schema parses correctly. Markdown rule for images is not triggered (the added AGENTS.md section has no images).

Fix these issues in Kilo Cloud


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

Review guidance: REVIEW.md from base branch main

…rship

discoverMembers returns descendants as well as roots, and subagent child rows
carry a null cloud_agent_session_id, so the every-entry test could never hold
for a root with children and the sandbox survived to the idle sweep.

The sole-owner test now counts only entries that own the worktree, requires
exactly one equal to the deleting session, and treats zero owners as not
exclusive. The committed manifest is also re-checked before returning
exclusive, so a competing root that commits between the membership read and the
manifest write cannot authorise a stop.
@eshurakov
eshurakov added this pull request to stack #6675 September 24, 2026 07:27
@eshurakov
eshurakov merged commit 8e3f5d0 into main Sep 24, 2026
31 checks passed
@eshurakov
eshurakov deleted the eshurakov/polished-delta branch September 24, 2026 07:48
iscekic pushed a commit to Kilo-Org/kilocode that referenced this pull request Sep 26, 2026
…4554)

## Automated docs sync — 2026-09-25

This PR keeps kilo.ai/docs in sync with features merged to [Kilo-Org/cloud](https://github.com/Kilo-Org/cloud) and [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode). Every change below links to the merged PR it documents.

- Window: `2026-09-24T07:08:33.639Z` → `2026-09-25T07:05:29.302Z`
- Verification (docs build + tests): **passing**

### Surface: `cloud-mobile`

- Assignees / requested reviewers: @iscekic and @eshurakov
- Derivation: Derived from the repository layout. A product surface is a package under packages/ that ships a distinct client, plugin, backend, or hosted service: cli = packages/opencode/ + packages/tui/ + packages/server/ + packages/sdk/ + packages/plugin/; vscode = packages/kilo-vscode/ + packages/kilo-web-ui/ + packages/kilo-ui/; jetbrains = packages/kilo-jetbrains/; gateway = packages/kilo-gateway/; web = packages/kilo-console/ + packages/kilo-indexing/ + packages/kilo-memory/ + packages/kilo-sandbox/. Docs route from the IA tree packages/kilo-docs/pages/ plus docs/jetbrains-vscode-settings-parity.md: each surface lists the pages sections that document it, and the per-platform pages under packages/kilo-docs/pages/code-with-ai/platforms/ map to the matching extension surface (the vscode/ directory to vscode, jetbrains.md to jetbrains). A doc path belongs to the surface with the longest matching prefix; a path that matches none of those prefixes falls to `other` (the explicit other prefixes are listed under other.docs). The cloud surfaces are derived the same way from the Kilo-Org/cloud layout: cloud-mobile = apps/mobile/, cloud-web = apps/web/, cloud-extension = apps/extension/, and cloud-agent = the cloud-agent packages under packages/ (packages/cloud-agent-sdk/ + packages/cloud-agent-profile/). A cloud source names its repository while a bare string still means this repository. The pages under packages/kilo-docs/pages/collaborate/ document the cloud web app (app.kilo.ai: teams dashboard, billing, SSO, adoption dashboard), so they route to cloud-web. No page under packages/kilo-docs/pages/ documents the browser side-panel extension yet, so cloud-extension lists no docs prefix.
- Map: `.github/docs-sync/surfaces.json`
- Surface map: `cli`, `vscode`, `jetbrains`, `gateway`, `web`, `cloud-mobile`, `cloud-web`, `cloud-extension`, `cloud-agent`, `other`
- Source prefixes: `apps/mobile/` (Kilo-Org/cloud)
- Doc prefixes: `packages/kilo-docs/pages/code-with-ai/platforms/mobile.md`
- Paths that fall to `other`: `packages/kilo-docs/pages/community/`, `packages/kilo-docs/pages/kiloclaw/`, `packages/kilo-docs/pages/contributing/`, `packages/kilo-docs/LEARNINGS.md`, `docs/`
- Reviewers are ranked from `Kilo-Org/cloud`; the workflow needs a token with `contents: read` on that repository (repository secret `CROSS_REPO_ACCESS_TOKEN`, exposed to the upsert step as `CLOUD_REPO_TOKEN`).
- How the two were computed: Reviewers for `cloud-mobile` are ranked from `Kilo-Org/cloud` git history over `apps/mobile/` (a commit 180 days old counts half as much, half-life 180 days). Bots (author type "Bot" or a login matching /\[bot\]$/i) and people without admin, write, or maintain permission are excluded.

### Changes

<!-- docs-sync:changes:start -->
| Docs change | Source |
| --- | --- |
| updated pages/code-with-ai/platforms/mobile.md | [Kilo-Org/cloud#6386](Kilo-Org/cloud#6386) |
| updated pages/ai-providers/openai-chatgpt-plus-pro.md | [Kilo-Org/cloud#6702](Kilo-Org/cloud#6702) |
| updated pages/code-with-ai/platforms/cloud-agent.md | [Kilo-Org/cloud#6683](Kilo-Org/cloud#6683) |
| updated pages/getting-started/byok.md | [Kilo-Org/cloud#6692](Kilo-Org/cloud#6692) |
<!-- docs-sync:changes:end -->

### Pending — will retry

<!-- docs-sync:pending:start -->
_None._
<!-- docs-sync:pending:end -->

### Considered, no docs change needed

<!-- docs-sync:skipped:start -->
| PR | Reason |
| --- | --- |
| [Kilo-Org/cloud#6658](Kilo-Org/cloud#6658) | Internal sandbox lifecycle fix with no user-visible workflow or setting. |
| [Kilo-Org/cloud#6673](Kilo-Org/cloud#6673) | Internal container CA trust plumbing, no user-facing behavior. |
| [Kilo-Org/cloud#6672](Kilo-Org/cloud#6672) | Internal sandbox launch/recovery fix with no documented workflow change. |
| [Kilo-Org/cloud#6660](Kilo-Org/cloud#6660) | Internal cloud-agent queue delivery fix; no new command, setting, or workflow for users. |
| [Kilo-Org/cloud#6226](Kilo-Org/cloud#6226) | Internal gateway alias-routing change, not user-visible. |
| [#14490](#14490) | Tool-call animation and streaming UI polish; users do not need to learn a new workflow. |
| [#14530](#14530) | Bug fix restoring intended worktree-pool behavior, no doc change needed. |
| [#14529](#14529) | Bug fix restoring tab/panel state across project switches. |
| [#14531](#14531) | Reconnect recovery bug fix, restores already-documented behavior. |
| [#14532](#14532) | Bug fix keeping session tab title in sync on rename. |
| [Kilo-Org/cloud#6088](Kilo-Org/cloud#6088) | Removes internal/admin model-experiment surfaces, not public product docs. |
| [Kilo-Org/cloud#6682](Kilo-Org/cloud#6682) | Internal control-socket reconnect race fix, no user-facing change. |
| [#14534](#14534) | Transcript re-render performance bug fix. |
| [#14535](#14535) | Bug fix preserving the loaded browser page across context switches. |
| [Kilo-Org/cloud#6684](Kilo-Org/cloud#6684) | Reverted by Kilo-Org/cloud#6685. |
| [Kilo-Org/cloud#6678](Kilo-Org/cloud#6678) | Dead-code constant removal, no user-visible effect. |
| [Kilo-Org/cloud#6687](Kilo-Org/cloud#6687) | Removes internal model-experiment maintenance and retains tables, no user-facing change. |
| [#14515](#14515) | JetBrains plugin unload crash fix, no documented behavior change. |
| [#14520](#14520) | JetBrains transcript/list rendering performance work. |
| [Kilo-Org/cloud#6614](Kilo-Org/cloud#6614) | Mobile PR Review header and session title bug fix, no doc change needed. |
| [Kilo-Org/cloud#6625](Kilo-Org/cloud#6625) | Internal mobile secure-store error-handling refactor. |
| [Kilo-Org/cloud#6624](Kilo-Org/cloud#6624) | Mobile auth bug fix that stops a retry loop; restores expected sign-in behavior with no new setting or workflow. |
| [#14310](#14310) | Contributor/CI fix making the kilo-v2 checkout installable; not user-visible product behavior. |
| [Kilo-Org/cloud#6611](Kilo-Org/cloud#6611) | Mobile notification-tap fix that selects the session's organization; restores correct behavior rather than adding a learnable feature. |
| [Kilo-Org/cloud#6644](Kilo-Org/cloud#6644) | Mobile sign-in layout/alignment polish; no change to what a user must do. |
| [Kilo-Org/cloud#6601](Kilo-Org/cloud#6601) | Mobile layout fix keeping empty states clear of the tab bar; purely visual. |
| [#14543](#14543) | CI/release infrastructure adding Windows binary code signing; no public docs impact. |
| [Kilo-Org/cloud#6616](Kilo-Org/cloud#6616) | Mobile visual defect fixes and a session-title fallback; no new user workflow or setting. |
| [Kilo-Org/cloud#6630](Kilo-Org/cloud#6630) | Reports an edge-case partial worktree restore; failure-path plumbing with no new user-facing workflow, target setting, or config. |
| [Kilo-Org/cloud#6699](Kilo-Org/cloud#6699) | Cloud Agent e2e stabilization plus internal idle-sandbox capacity handling; not user-visible. |
| [#14545](#14545) | Automated JetBrains release/changelog PR; underlying user-facing changes are triaged from their own PRs. |
| [Kilo-Org/cloud#6708](Kilo-Org/cloud#6708) | Internal AI-gateway request-logging policy change in the admin panel; no existing public docs surface and no change to how users run Kilo Code. |
| [#14533](#14533) | Documentation already shipped with the merged PR. The experimental.task_model_selection flag is gone from the current source, and pages/code-with-ai/agents/model-selection.md, pages/code-with-ai/agents/context-mentions.md, and pages/getting-started/settings/index.md already describe per-task selection as default-on with no stale experiment references. |
| [#14510](#14510) | Documentation already shipped with the merged PR. Marketplace companion-skill support is present in the current source (packages/opencode/src/kilocode/marketplace/companions.ts and installer), and pages/customize/marketplace.md already documents installing, publishing, and removing MCP servers with companion skills. |
<!-- docs-sync:skipped:end -->

---

(bot) Generated by the docs-sync workflow. Humans review and merge; while this PR stays open, the next daily run appends new changes here. Branch: `docs/auto-sync-2026-09-25`.
<!-- docs-sync: processed-through 2026-09-25T07:05:29.302Z -->
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