Repository navigation
review: billing order, tun addresses, silkd process groups, SDK cancel, docs - #481
Merged
Merged
Conversation
notify reports an inotify overflow as an Other event with the Rescan flag, not as an error, so to_frames dropped it and the watch went on with a gap that docs/silkd.md says never happens.
…s exec does The session shell ran 'cd <dir> || exit 1' in its init line, so a missing cwd surfaced as 'internal: shell exited'. The cwd is now the child's working directory and spawn errors share exec's mapping.
kill, the SDKs' cancel and timeout kill, and a foreground client that hangs up reached only the direct child, so 'sh -c "a | b"' left a and b running after every timeout. Sessions already ran in their own group; exec and its kill now do the same.
A parked keepalive connection skipped the dial, the one step that checked the ctx, so a canceled call still ran its verb in the guest while the caller saw context.Canceled; Run started a command it would never kill.
…refuses tun The list lived in three copies (the API refusal, the seed hash, reload's diff) and #478 added tun to only one: PUT /v1/pools answered 200 for "tun": true and dropped it, and a tun edit made every restart warn that the pools differ.
The event waited for the egress arm, so a crash between the claims.json commit and the arm restored a served claim that no event billed. A batch the arm rolls back now records a release for each claim.
Release takes no Transition lock, and wake, hibernate, archive and unarchive stamped their usage event after the claims commit, so a release committing in between was journaled first and the late event reopened a span to the deadline, or forever for an archive kept forever. A graceful stop had the same gap: eng.Close returned when the last hibernate CLI exited and main exited before the hibernate event. Hibernate, wake and a settled intent now record their event inside the m.mu section that publishes them; archive and unarchive stamp the time there. An archived wake also records unarchive and arms its egress door before the checkpoint store delete, not after it.
…'s span A VMM restart recorded vmm_restart only after the probe and the claims commit, so a stop or crash during the probe (up to 90 s) left a running guest billed as ended. vmm_restart now follows the start under m.mu, and every stop after a start records vmm_exit. failLocked records vmm_exit before vmm_failed when the exit was not recorded yet, so an interrupted capture whose guest does not resume no longer stays billed running until release (docs/deploy.md promises both events).
Both events were stamped after the claims commit, so a release committing in between was journaled first and the late adopt or move_in reopened a span to the deadline. adopt takes its time and deadline under m.mu; move_in records inside the commit's publish. A moved guest that cannot start lets failLocked record its vmm_exit.
…or can adopt Reconcile ended a claim whose VM was gone with a vm_gone reap and freed its overlay address even when a move journal said the other node runs it, and freed the address of a replicated claim whose registry row stays adoptable, so a later claim could answer for the same address. Such claims now keep both; purging the row frees its address.
…enced Peers may adopt a replicated claim once the fence trips, but the liveness pass still journaled vmm_exit and cold-booted it with vmm_restart, which the cross-node fold reads after the peer's adopt: the adopted claim billed as running compute. docs/cluster.md says a fenced node only keeps the guest running until it returns.
An adopt whose claims.json write failed forgot the claim without the Transition lock, while a concurrent wake of the just-published claim took and consumed its registry row and then found the claim gone, so the checkpoint was deleted and every later Lookup answered 404. The adopt now holds the new claim's Transition across the commit, and a wake whose claim left during the take stops before consuming the row.
finalizeBatch set the claim's layer only after the inject check, so a pooled key whose pool has no egress resolved to the tenant class during the check: the claim answered 200 for an inject that no proxy would ever apply, while the same env through PATCH .../env answered 400.
destVerdict reported any address an allow entry covered as a re-admitted internal one, public addresses included, and the upstream path dials those direct. With the documented guard-off allow (0.0.0.0/0, ::/0) and egress_upstream, every connection left from the node's own address instead of the claim's upstream, and its audit named no upstream.
The PostgreSQL step's package list left out tunnet, so tunnet/pg's tests skipped and its SKIP check never saw them.
A relay or exec wake can wait on sb.Transition behind a hibernate. It then restored the guest under a detached ctx, recorded a wake and left it running and billed until the idle sweep or the deadline, with nobody connected. It now returns the ctx error once it holds the lock.
One failed LockedTaps call at boot quarantined every live egress-lane claim and removed its VM, though Relock is idempotent and settles each tap on its own. A failed listing now falls back to relocking each tap.
The target dropped a reservation 2 minutes after admission, while the source's baseline capture first waits on the dump slots it shares with hibernates and saves and then writes all guest memory, so a large guest on a slow run dir always failed its first round with 404. The idle drop now matches the 15-minute dump timeout; a retried move still replaces its reservation at once.
forward charged the claim's quota and opened and billed a flow before it looked the exit up, so frames dropped as no_exit (a PUT /tun profile not in tun.exits, or a reload that removed a label in use) were billed as egress_bytes and consumed quota.
…y read #480 touched the claim through Sandbox.Touch, which reads the clock again on every packet in both directions; TouchAt takes the time the flow check read.
The fake underlay's Recv returned whenever its channel was momentarily empty, so the router flushed the coalescer between injected segments and TestDeliverCoalescesInOrderSegmentsIntoOneGSOFrame failed intermittently under -race; the no-TSO merge test passed vacuously the same way. A batch now arrives in one Recv.
…mits Release, reap, rollback and quarantine freed the address only after the VM teardown, which can take minutes. A crash in between left the claim gone from claims.json with no release on record, so the restart treated the address as free with no reuse window and a new claim could inherit the old flows' state at the exit.
A release that failed while the shared meta store was down was only logged, and nothing swept the table, so each failure held one address for good. A refused release is now kept and retried on the hourly store sweep.
… the cocoon root Only archive-e2e.sh said so; run beside a serving sandboxd on the same root, the others wipe its claims. The release workflow comment no longer claims only dist-extra/ is gitignored.
…, test helpers lose restating docs The pty comment said the pump hangs without the drop; since the exit drain got its timeout, held slave dups only make it run to that timeout.
… private helper Multi-line godocs across sdk/go and protocol/wire fold to one line each (a stale Promote parenthetical goes); frame.go's unexported types move below the exported ones; Python drops the _post_json forwarder and makes require_field private.
…y, as the Go SDK does
…, vocabulary types ahead of volumeCatalog Stale and restating comments go (the liveness one is contradicted: the tap survives a VMM exit); a failed golden snapshot rm that blocks the next build logs at Warn; reconcile reuses parseCheckpoint; planTun returns one tunPlan so commitTun's signature fits a line; volumeHolders and catalogVolume move ahead of volumeCatalog.
…ts, mustClaim reuse
…opped one's staging Reconcile armed a MoveIn claim's doors and tun before its move record resolved, so after an aborted move both nodes announced the overlay address until dropMoveIn; the resolve arms it (a running guest on start, a stopped one through the cold restart). The staging sweep kept the guest-memory files of a MoveIn claim that the restart dropped because it checked the loaded set instead of the adopted one.
The digest went through a plain write and rename while meta.json was fsynced, so a power loss right after a publish could leave a template whose digest reads empty and whose digest-guarded delete or labels call answers 409.
…load Reloaded read sb.VsockSocket without the pool lock that a cold restart or a move writes it under; a torn string read is a crash. The socket now comes through TunState, which already reads the claim's tun fields under that lock.
Nothing outside its own test read it, and no audit record carries a name, so every kept A/AAAA answer paid a mutex and a map write for nothing.
Stale and restating comments go or are corrected (Engine.Version no longer names a master-<sha> form cocoon never prints, the outbound Resolve godoc, the address log's compaction bound); own-line comments start uppercase; vocabulary types move ahead of their users in server/move.go, tunnet/gro.go and tunnet/udp; the relay's 101 reply builds on upgradeProto; Local's one-caller compact is inlined.
…no redundant build tags
…e notes and duplicates Drift the readers found is fixed against the code (each change cites its line in the round's ledger): tun traffic resets the idle clock, downtime_ms includes the dump-slot wait, a disarm only stages the tun quota counter, a promote of a claim whose tun is off answers 409, the tun pool flag is config-owned, and the shared tun address table gets its DDL. Upgrade notes for releases older than any running fleet, the duplicated /v1/info payload and stale version history go; silkd's README lists the DNS relay, tun verbs and IMDS.
…w, De Morgan, empty loop body)
… never retry the release of an address its claim holds again Codex review round 1. An arm failure recorded the batch's release events only after the VM teardown, so a crash there left their claim spans open to the deadline; they now follow the drop commit. A release the address store refused could be retried after a later enable had assigned the same PG row back to the claim (Assign is idempotent per claim), freeing a live allocation; the retry drops an entry whose claim holds that address.
…s, and a rollback bills before it frees addresses Codex review round 2. Assign is idempotent per claim, so two concurrent enables of an addressless claim got the same address and the one that lost commitTun's state check released the address the winner had just published; it now releases only an address the claim does not hold. A rolled-back batch writes every release event before any address-store call, so a slow store cannot hold later events back.
This was referenced Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Whole-repo static audit of main (376a7a9 plus #480). Every production file was read in full by a per-file reader, and 12 flows (claim lifecycle, warm pool, settle/hibernate, failover, capture/store, live move, live volumes, egress metering, API relay, silkd protocol, cocoon CLI + restart, advanced network) were walked window by window. No hardware E2E ran in this round.
Bugs fixed (one commit each, each with a regression test that fails on the old code unless noted)
Billing
eng.Closereturned when its CLI exited and main exited beforerecordHibernate. Fixed by the same change.claimevent. The event now follows the commit; an arm rollback recordsreleasebefore its teardown.vmm_restartonly after the probe (up to 90 s), so a stop in that window left a running guest billed as ended; an interrupted capture whose guest did not resume gotvmm_failedwithoutvmm_exit.vmm_exit/vmm_restartafter a peer may have adopted it.vm_gone) and freed the overlay address of a dropped claim whose move journal says the other node runs it, and freed the address of a replicated claim whose row stays adoptable.no_exitwere charged to quota and billed asegress_bytes.Correctness
PUT /v1/poolsaccepted"tun": trueand dropped it; the config-owned field list lived in three diverged copies and now has one owner.egress_internal_allowre-admitted public addresses as internal, so the documented guard-off allow plusegress_upstreamdialed every connection direct from the node.LockedTapsat boot quarantined (removed) every live egress-lane VM.Reloadedread a claim's socket without the pool lock.killsignals it); a bad session cwd answeredinternal.Cleanups
Dead
Host.ResolvedNamecut; stale and restating comments removed, layout and godocs to the house rules, test helpers deduplicated, a flaky GRO test fixed, docs matched to the code (release-scoped upgrade notes and duplicates cut).Numbers
Gates (macOS and a linux/arm64 container)
make go-lint:0 issues.×10 (5 modules × linux/darwin) andfmt --diffclean.make go-test(race, plus one pass of every benchmark) green in all 5 modules.-D warnings,cargo testgreen on macOS and inrust:1linux/arm64, including the root-only--ignoredsuites.Coverage
Kept without code (to be added to #433)
Assignwhose reply is lost after commit.tun_down(documented).Open for the owner
Lookupon a 404 after a move or evacuation? Today onlyClosedoes, so the MCP server and the adapters keep a stale owner until the lease ends.