Skip to content

review: whole-repo round on main 21dc4e7 - #214

Merged
CMGS merged 29 commits into
mainfrom
review/whole-repo-0917
Sep 17, 2026
Merged

CMGS merged 29 commits into
mainfrom
review/whole-repo-0917

Conversation

@CMGS

@CMGS CMGS commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Whole-repo audit on main 21dc4e7: every Go, Rust and Python file read in full against /code, /code-rs, /code-py; the docs read against the code; /simplify and /loc-justify lenses over every package. Fixes land first, each in its own commit with a regression test where one is cheap; the review commits are behaviour-preserving.

Fixes

  • egress door cap (7dde24e): netutil.LimitListener wrapped the accepted conn in a type embedding the net.Conn interface, so the *net.UnixConn lost CloseWrite and the splice fast path. A tunnel whose upstream closed first never delivered EOF to the guest and held two descriptors and a door slot until the claim ended. The cap now lives in a listener whose conns embed the *net.UnixConn. Test: TestEgressDoorHalfCloseReachesTheGuest fails on main with i/o timeout, passes here.
  • archive-delete marker is fsynced; fork-* export dirs interrupted by a restart are swept by Reconcile (9af3df9).
  • config: duplicate pool keys (first-wins side maps under a last-wins pool), duplicate volume paths (defeated writer exclusion) and a preview_advertise that names no host (http://:8443/p/…) are rejected at load (6516196).
  • journald records no longer carry ANSI colour (349e6f1).
  • SOCKS5 refuses a zero-length domain name; an intercepted CONNECT is recorded as a decision (1de3ca4).
  • effectivePolicy resolves against the active pool set (2431316); the checkpoint TTL sweep runs off the Run loop so an s3 backend no longer holds the first refill (fe5ec58).
  • sdk/go: the proto probe is skipped when nothing can park (one dial saved per handle with WithKeepAlive(0) against a proto-1 daemon); Checkpoint.New refuses WithVolumesAttachOnly instead of dropping it; Lookup keeps the peer-discovery error (5e97899).
  • mcp: claims are released on SIGTERM, not only on stdin EOF; exec/spawn refuse an empty command instead of running sh -c "" (a78dd82).
  • silkd: a request pipelined behind a find no longer truncates the walk without a terminal frame (find_serves_a_request_pipelined_behind_it_after_done hangs on main); write_frames writes the frames rendered before an oversized one; a failed pty started write returns the error like exec (9b7829b).
  • boot/init: /dev/vdX ids, as cocoon's Firecracker lane passes them, resolve (c7d8bc6).
  • sdk/python: run(timeout) bounds the proto probe (the handle's first RPC could hang forever); a truncated 200 or a frame without a required member raises APIError/ProtocolError instead of KeyError; Session.close/Lsp.stop are idempotent (a with lsp.request() exit used to raise not_found); Pty.read latches EOF and Pty.write chunks like every other bulk path; Watcher.error stays None after a close from another thread; SandboxTimeout joins the hierarchy while remaining a TimeoutError (2aeff90).
  • adapters: the OpenAI provider releases a claim whose session state fails to build; the LangChain read tool raises ToolException for a missing path as its description promises (e4e0d5a).
  • protocol: TestEveryVerbHasAFixture covers the four LSP types; resp_info.json says proto 2; req_fs_find.json carries a glob that matches something; the Rust corpus test round-trips every response (f1bfc4e).

Review commits

  • 0ed354f simplify/dedup (prod −135 lines net): one owner for settle-and-bill, the destroy fan-out, the cancel-then-close pair, the regular-file export contract, the chunked write loop; Event.Sandbox/Tenant and journal.close had no production reader/caller.
  • 8993167 declaration layout per /code File Organization (pure moves; asl clean on both GOOS).
  • a496fec comment budget: 26 comment lines added, 76 removed across Go; Rust and Python tightened in their own commits.
  • d945161 docs aligned with the code (audit_log-conditional audit lines, exec 504 semantics, keep-alive caveats and the 8-connection cap, PreviewURL's api token, ReadFileTo, the Python bytes-not-streams exception, the cold-boot range, packaging/, stamps on unstamped benchmark rows, the preview_advertise host requirement, a wall-clock-bounded desktop warmup).

Gates

GOWORK=off make go-lint            → 10 × "0 issues." (5 modules × linux/darwin), fmt --diff clean
go test -race -count=1 ./...       → 18 ok across protocol/wire, sandboxd, sdk/go, e2e, mcp
asl ./... (darwin + linux)         → 0 findings (4 pre-existing forwarder advisories, recorded KEPT)
cargo fmt/clippy -D warnings/test  → boot/init 15 (mac) / 17 (linux) tests, silkd 28 unit + 11 e2e suites, mac and rust:1 linux/arm64
ruff format --check / ruff check   → 33 files formatted, all checks passed
pytest                             → cocoonsandbox 204, openai 7, langchain 9; mypy strict: no issues
make sh-lint                       → clean

Size

Branch diff: prod code +799 −683 (net +116), test code +555 −409, docs +65 −47. The prod increase is the fixes (door limiter, config validation helpers, closeOnCancel, RequireRegular, require_field/_need, SandboxTimeout, the MCP signal path); the review commits are net negative (−135 in the simplify commit, −50 comment lines in the budget commit).

Hardware

Baseline on main 21dc4e7 (cocoon-test1, isolated cocoon root, ghcr 24.04-5158a14 guests): sandboxd-e2e 22.0 s, egress none 3.9 s / egress 7.2 s, socks 16.3 s, intercept 3.8 s, archive 41.4 s, desktop 51.4 s — 7/7 PASS, warm claims 0.4–1.2 ms. The same seven legs on this branch's binaries and a silkd overlay follow as a comment.

CMGS added 24 commits September 17, 2026 15:50
netutil.LimitListener wraps every accepted conn in a type that embeds the
net.Conn interface, so the *net.UnixConn the doors hand out lost CloseWrite
and the splice fast path. A guest tunnel whose upstream closed first never
saw EOF, and each such tunnel held two descriptors and one door slot until
the claim ended. The cap now lives in a listener whose conns embed the
*net.UnixConn itself, so half-close and splice(2) work again.
… exports

The delete-intent marker was written without a directory sync, so a crash
between the marker and the checkpoint delete could lose the marker while
the journal (which is synced) already dropped the claim: the wake image
stayed in the store with nothing left to reclaim it. The marker now rides
the same syncDir the volume dirty marker uses.

A fork export interrupted by a restart left <data_dir>/fork-* at full image
size; Reconcile now sweeps it beside the golden *.tmp staging dirs.
…outable preview_advertise

Two pool entries resolving to one key were accepted at load and seeded
last-wins for the pool but first-wins for its egress and warmup maps, so
the node ran the second entry's warm target under the first entry's policy;
PUT /v1/pools already rejected the same input. Two volumes sharing a path
under different names defeated writer exclusion, which is keyed by name.
preview_advertise defaulted to a wildcard preview_listen and minted
http://:8443/p/... URLs no client can dial; it now needs a host like
advertise_addr does. The preview handler routes an unmapped claim error
through writeResult instead of dropping writePoolErr's verdict.
zerolog's ConsoleWriter colourises unless NoColor is set, so every record
reaching journald carried escape sequences around the timestamp and level.
One constructor now serves the daemon and the tests.
…ONNECTs

A zero-length domain name passed the handshake, matched a wildcard rule and
was recorded as an allow before the dialer refused it; RFC 1928 calls it
malformed, so the handshake now answers a general failure. An intercepted
CONNECT was never recorded as a decision: only the inner requests were, so
a guest that reached an intercepted host and aborted the TLS handshake left
no trace.
A pool leaving the desired set stays in the map while a refill is in
flight; a tenant claim of that key in the window was treated as pooled and
served with no egress door, then got the tenant policy two seconds later.
The sweep lists every store record and deletes expired ones serially; on
the s3 backend that held the first refill, so warm pools stayed empty and
every claim in the window cold-booted. The function already guards itself
with a CAS, so it detaches like retryArchiveDeletes.
…h-only checkpoint claims

connect probed the daemon's proto on every handle's first call, but the
lease only parks a connection when the platform can probe it and the
keep-alive window is open, so WithKeepAlive(0) against a pre-proto-2 daemon
paid a second dial per handle for a reuse that never happened. The probe
now runs only when its answer can matter.

Checkpoint.New rejected WithVolumes and WithClaimRef but let
WithVolumesAttachOnly through, where the wire request silently dropped it.
Lookup keeps the peer-discovery error instead of reporting a missing
sandbox when discovery never ran, Connect documents that a seed list keeps
its first address, and the EOF checks use errors.Is.
A host that terminated the stdio server with a signal skipped the deferred
release, so every sandbox the session claimed stayed held for its one-hour
tool TTL. main now runs serve beside a signal context and releases on
either exit. exec and spawn ran sh -c "" and reported exit 0 when the
required command argument was missing; both refuse it now.
…a failed pty start; flush frames before an oversized one

A find ended silently on any readable byte, so a request pipelined behind
it (legal since proto 2) truncated the walk with no done or error frame and
left the client waiting; the walk now treats buffered bytes as a queued
request and only a hang-up as the end.

write_frames rendered a batch into one buffer and returned on the first
frame over the cap, discarding every frame rendered before it, so a find
that hit an oversized match dropped the matches ahead of it; the prefix is
written before the error.

pty::open swallowed a failed started write and returned Ok, so the server
awaited a feeder on a connection whose writer was gone; it returns the
error like exec does. Chunk::into_response lost its unreachable stdout and
stderr arms, and the exec foreground channel is built once instead of
cloned twice.
…s disk ids

cocoon's Firecracker backend names layer and COW disks by device path;
resolve_disks matched ids against virtio serials only, so such a boot
polled sysfs for the whole budget and powered off. A /dev/... id now
resolves as soon as its node exists, mirroring the shell hook this
initramfs replaced.
TestEveryVerbHasAFixture listed 35 of 39 requests and 20 of 21 responses;
the four LSP types were outside the guard. resp_info.json pinned proto 1
against a daemon that serves proto 2, and req_fs_find.json carried a glob
that matches nothing (.rs anchors to a file literally named .rs).
…setup messages

watch_e2e reimplemented send and next_frame inline; pty_e2e accepted any
exit code for exit; two error tests checked the type without the kind; the
harness panicked without the offending line or git's stderr; request_on
was pub with no external caller.
exec.rs and pty.rs import proto with self like the other ten modules;
proc.rs calls sysutil through its import and names the ring snapshot for
what it does; find.rs spells the Arc clone explicitly. Comments that
restated the code or misnamed their subject go (debug_token, MNT_SECURE,
the console note, the duplicated ip= layout); two inline comments move to
the lowercase register; FIND_MAX_FILE's doc names both verbs it bounds and
BootCfg.timeout its real scope. lsp.rs drops a discarded bool binding and
cfg.rs indexes a slice its guard already proved long enough.
…eld, make close idempotent, chunk pty writes

run(timeout) left the handle's first RPC unbounded: the proto probe ran
before the watchdog on a socket with no timeout, so a wedged guest hung
the call forever. The probe now runs under the remaining budget.

A 200 reply or a silkd frame missing a required member raised KeyError
past the SDK hierarchy; require_field and _need turn those into APIError
and ProtocolError. Session.close and Lsp.stop raised not_found on a second
call (and Lsp's with-exit after a request stream, which reaps the server).
Pty.read now latches EOF like PortConn and Pty.write chunks like every
other bulk path, so a large paste no longer emits a frame over the cap
that killed the guest shell without a word. Watcher.error stays None after
a close from another thread, as documented. SandboxTimeout joins the
SandboxError hierarchy while still being a TimeoutError. The bare data
frame renders without a str round trip (3x on 1 MiB chunks). Tests: the
four raw-reply cases share one fixture, FakeNode lives in conftest, and
test_proxy reads the monotonic clock.
…port a missing file as a tool error

The OpenAI provider claimed a sandbox and then built the session state;
a bad snapshot spec raised past the claim and leaked it for its TTL. The
LangChain read tool promised a tool error for a missing path and raised a
Python exception into the agent loop instead; it now raises ToolException
under handle_tool_error.
Pool: one owner for settle-and-bill (settlePendingSnap), for the destroy
fan-out (destroyAll) and for the needs-unmount predicate; reapOnce compacts
with slices.DeleteFunc; Audit drops a guard its only caller already makes;
applyVolumes loses a single-volume arm the errgroup path already covers;
resolveGolden returns a zero value with a nil-safe release instead of six
no-op closures; archiveCkPinned reads the pinned set; claimLoaded rejects
before it materializes an export; the reconcile lock step tests locked[tap]
once. Server: closeOnCancel owns the cancel-then-close pair, the unknown
sandbox reply goes through writePoolErr, handleDeleteTemplate builds a
PoolKey instead of a request body. Egress: nonInjectable is a slice,
marshalCA takes the concrete key types, Event loses two fields nothing read
and Proxy the identity that fed them. Store: RequireRegular owns the
regular-file contract for both backends, s3 names its concurrency and batch
constants and joins keys explicitly. Mesh inlines persistEpoch. mcp looks a
tool up with slices.IndexFunc. sdk/go: sendChunks owns the chunked write
loop. e2e: meshsmoke and crossnode use harness.Claim, lifecycle bounds its
run, egresssmoke drops a condition its guard made constant, interceptsmoke
folds case on both sides of grepLine. journal.close had test callers only.
Tests: claimTenant, a table for validateHealedCheckpoint, testCA over
testing.TB, one table for Client.Volumes, postExec over postJSON,
new(*expr) in the s3 fake.
Wire pairs and vocabulary types cluster ahead of the type they serve
(claimResponse after claimRequest's methods, checkpoint and preview wire
types above their handles, volume admission types above catalogVolume);
utilities sit below every type block (mesh.go short/containsAll,
frame.go NewFrameScanner in the decode group); producers trail the type
they produce (Compose below composite, resolveGolden below
goldenResolution); the record-lock layer moves out of template.go into
reclock.go, lockedVMName joins the pool.go leaf helpers, the sandbox list
reply and handler leave metrics.go for server.go, and Config groups its
fields by concern instead of one blank line per field.
Godocs on unexported funcs that restated their bodies go (archiveOnce,
archive, publishCheckpoint, sweepExpiredCheckpoints, idleOnce, skipIdle,
wake, respFail, parseRecord, loadEpoch, the five sdk/go RPC helpers, the
decodeReq/decodeResp adapter note, two rpcbench samplers); multi-line
narrative on unexported items compresses to one STE 101 line (frame.go's
encodeTagged/frameTag/scanTag, the silkdtest fakes, mcp's server and
cappedOutput, the mcp package doc drops product names); inline comments
that repeated a sibling or misnamed their subject are fixed (Create ->
O_CREATE, the archive rollback and commit notes, the reapAction consts,
journal.VolumesRW, the storetest phase labels); godocs that stated a
contract the code does not have are corrected (store.Fetch's release,
PreviewDial's authorization, clearHealPending's lock, the two-peer cap on
VolumeOwners and TemplateVolumeOwners). 26 comment lines added, 76 removed.
egress: audit lines exist only under audit_log and carry no tenant; the
intercepted CONNECT is now a recorded decision. API: exec answers 504 for
timeout_seconds only, writable is never emitted on peer-only volume rows.
SDKs: the keep-alive window is Unix-only and parks at most 8 connections,
PreviewURL/preview_url take the api token, ReadFileTo joins the Go files
reference, the Python guide states its bytes-not-streams exception and the
type key every frame-derived dict carries, the adapter table names the
per-call user refusal, the LangChain README imports langgraph. README and
index quote the measured cold-boot range, list packaging/, describe make
help honestly and add make bench; the boot table admits /dev paths and the
shared wait budget. benchmarks and performance stamp the rows that lacked a
host or commit. deploy documents the tenant egress field, the root-only
stats route and the preview_advertise host requirement. desktop's warmup
example is bounded by wall time under the 2-minute engine cap. Three image
headers match what their RUN steps install.
…binary; python.yml lints e2e

The environment stamp bench.sh emits had no sandboxd row, so every pasted
results block needed its commit added by hand and three of them never got
one. The prebuilt guard admitted a run with only SANDBOXD_BIN and DEMO_BIN
set and then died on an unbound RPCBENCH_BIN under set -u. python.yml
triggers on e2e/**/*.py, so e2e/tls_client.py is linted on its own changes.
The half-close test closes the CONNECT reply body and stops shadowing err;
validate delegates the preview and pool-key checks so gocyclo stays under
30; the reconcile lock step keeps its two flat ifs (nestif); a test message
spells color the way misspell wants.
…'s original error, size lifecycle's deadline from -wait, correct the boot timeout doc

A claim finishing between closeBoxes and process exit was recorded too
late and held for its TTL: trackBox now releases a claim tracked after the
session closed, and main closes stdin on a signal so the read loop's own
release runs, falling back to closeBoxes after a grace. The OpenAI
provider's cleanup release no longer replaces the construction error it
was cleaning up after. lifecycle's overall deadline follows -wait instead
of a fixed 20 minutes. cocoon.timeout covers the disk set only; NIC MACs
have a fixed 200 ms budget.
@CMGS

CMGS commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Hardware: seven E2E legs on this branch's binaries

cocoon-test1 (384c, Debian 13, isolated cocoon root /data00/probe-claude, CH v54.0.0), sandboxd pr-4cdb94c + the seven drivers cross-compiled from this branch; rt guests = rt:24.04-5158a14 with this branch's silkd overlaid (rt-pr:24.04), desktop guest = desktop:24.04-5158a14. Same kit, same host, same day as the main baseline.

leg main 21dc4e7 PR 4cdb94c
sandboxd-e2e (warm/miss/reap/reconcile + smoke, bridge) PASS 22.0 s PASS 22.0 s
egress none PASS 3.9 s PASS 3.8 s
egress egress PASS 7.2 s PASS 7.2 s
socks PASS 16.3 s PASS 14.2 s
intercept PASS 3.8 s PASS 3.8 s
archive (idle → hibernate → archive → wake) PASS 41.4 s PASS 41.3 s
desktop (none + egress lanes) PASS 51.4 s PASS 54.3 s

Warm claims on the PR run: 0.4–1.2 ms (iter=0 claim=1.2ms, then 0.4/0.5 ms); smoke steps exec/files/session/find/replace/watch/git/pty/egress all ok; no sbx- VM left behind.

… adapter's dual-failure case is pinned

closeBoxes is single-flight: it releases every tracked claim concurrently
and returns only once they are done, and a second caller (main's grace
fallback racing the read loop's defer) waits for the first instead of
finding an empty map and letting the process exit mid-release. Test:
TestCloseBoxesWaitsForEveryReleaseOnce. The adapter test now also raises
from Sandbox.close and asserts the snapshot error still propagates.
…ite, lookup discovery error)

Run starts the retention sweep asynchronously so a slow store cannot delay
the first refill; the test blocks Metas and asserts a warm VM still appears.
The pty test injects a writer that fails on the started frame and asserts
the error propagates, the table entry is gone, and the shell is dead.
Lookup's test breaks peer discovery and asserts the peers error is joined
alongside the no-owner verdict.
namesHost required a scheme or a port, so a bare hostname failed startup
validation although Mint prepends http:// to exactly that shape. The
schemeless branch now treats a string without a port as the host and still
rejects an empty or unspecified one.
Mint prepends http:// to a value without a scheme, so validation now parses
the same string. A bare "[::]" kept its brackets past SplitHostPort and
slipped through as a routable host.
@CMGS

CMGS commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Hardware: E2E reruns on the review-round binaries

Same kit and bare-metal host as the previous comment (isolated cocoon root, CH v54.0.0). sandboxd and the seven drivers rebuilt from each commit below; rt guests keep this branch's silkd overlay, desktop guest = desktop:24.04-5158a14. 1ca3f1f and 07a8e1c only change config validation (preview_advertise shapes), so their runs exercise the same runtime code as 063c58d.

leg main 21dc4e7 PR 4cdb94c PR 063c58d PR 1ca3f1f PR 07a8e1c
sandboxd-e2e (warm/miss/reap/reconcile + smoke, bridge) PASS 22.0 s PASS 22.0 s PASS 22.5 s PASS 22.2 s PASS 22.2 s
egress none PASS 3.9 s PASS 3.8 s PASS 3.9 s PASS 3.8 s PASS 3.8 s
egress egress PASS 7.2 s PASS 7.2 s PASS 7.3 s PASS 7.3 s PASS 7.2 s
socks PASS 16.3 s PASS 14.2 s PASS 15.5 s PASS 13.8 s PASS 14.1 s
intercept PASS 3.8 s PASS 3.8 s PASS 3.8 s PASS 3.9 s PASS 3.8 s
archive (idle → hibernate → archive → wake) PASS 41.4 s PASS 41.3 s PASS 41.4 s PASS 41.4 s PASS 41.4 s
desktop (none + egress lanes) PASS 51.4 s PASS 54.3 s PASS 54.5 s PASS 54.0 s PASS 53.9 s

No sbx- VM left behind after any run.

@CMGS
CMGS merged commit 06b2728 into main Sep 17, 2026
5 checks passed
@CMGS
CMGS deleted the review/whole-repo-0917 branch September 17, 2026 10:28
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