From d3c8fb74374d286c4a209bc51ca59d2695f014f2 Mon Sep 17 00:00:00 2001 From: Austin Wang <38676809+austinywang@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:45:00 -0700 Subject: [PATCH 01/14] test(cloud): guest DNS must survive platform resolver SERVFAIL (#26640) Behavioral tests for a guest stub resolver and resolv.conf guard: the platform's answer wins, a SERVFAIL or silence falls back to the last good platform answer and then DNS-over-HTTPS, never to a fallback NXDOMAIN. Red on the base: the stub does not exist. Co-Authored-By: Claude Opus 5.5 --- web/tests/vm-devbox-dns.test.ts | 210 ++++++++++++++++++++++++++++++ web/tests/vm-devbox-image.test.ts | 1 + 2 files changed, 211 insertions(+) create mode 100644 web/tests/vm-devbox-dns.test.ts diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts new file mode 100644 index 000000000000..322384e39b4f --- /dev/null +++ b/web/tests/vm-devbox-dns.test.ts @@ -0,0 +1,210 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { runChild } from "./helpers/run-child"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { DEVBOX_DNS_PATH, DEVBOX_DNS_UNITS, devboxDnsCheckCommand } from "../scripts/devbox-image-common"; + +// cmux issue 26640: the platform resolver (10.32.0.28) answers SERVFAIL or +// nothing for every name at once, and the platform intercepts every port-53 +// packet, so the guest stub must route around it without breaking the names +// only the platform answers. +const script = path.join(import.meta.dirname, "../services/vms/images/devbox/cmux-dns"); + +const directories: string[] = []; +afterEach(() => { + for (const directory of directories.splice(0)) rmSync(directory, { recursive: true, force: true }); +}); + +function fixture(): string { + const directory = mkdtempSync(path.join(tmpdir(), "cmux-dns-")); + directories.push(directory); + return directory; +} + +/** Runs `body` with `dns` (the stub module) and `q(name)`, `reply(query, rcode, answers)` helpers. */ +async function python(body: string, ...args: string[]) { + const result = await runChild("python3", ["-c", String.raw` +import importlib.machinery, importlib.util, struct, sys +sys.dont_write_bytecode = True +loader = importlib.machinery.SourceFileLoader("cmux_dns", sys.argv[1]) +spec = importlib.util.spec_from_loader("cmux_dns", loader) +dns = importlib.util.module_from_spec(spec); loader.exec_module(dns) +def q(name, qtype=1, ident=0x1234): + labels = b"".join(bytes([len(p)]) + p.encode() for p in name.split(".")) + return struct.pack("!HHHHHH", ident, 0x0100, 1, 0, 0, 0) + labels + b"\x00" + struct.pack("!HH", qtype, 1) +def reply(query, rcode, answers=0, tc=False): + return query[:2] + bytes([0x81 | (0x02 if tc else 0), 0x80 | rcode]) + query[4:6] + struct.pack("!H", answers) + query[8:] + b"\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04\x0a\x20\x00\x1c" * answers +calls = [] +` + body, script, ...args]); + expect(result.stderr).toBe(""); + return result.stdout.trim().split("\n"); +} + +describe("cmux-dns resolver", () => { + test("the platform's answer wins while it works, so steered names keep resolving to the edge", async () => { + expect(await python(String.raw` +def exchange(server, query, timeout, tcp): + calls.append(server) + return reply(query, 0, 1) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +out = r.resolve(q("api.anthropic.com")) +print(dns.rcode(out), out[-4:].hex(), calls) +`)).toEqual(["0 0a20001c ['10.32.0.28']"]); + }); + + test("a platform SERVFAIL is retried, then answered from the last good platform answer", async () => { + expect(await python(String.raw` +state = {"down": False} +def exchange(server, query, timeout, tcp): + calls.append("platform") + return reply(query, 2) if state["down"] else reply(query, 0, 1) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +r.resolve(q("api.anthropic.com", ident=1)) +state["down"] = True +calls.clear() +out = r.resolve(q("api.anthropic.com", ident=7)) +print(dns.rcode(out), struct.unpack("!H", out[:2])[0], out[-4:].hex(), calls) +`)).toEqual(["0 7 0a20001c ['platform', 'platform']"]); + }); + + test("a name the platform never answered falls back to DNS-over-HTTPS", async () => { + expect(await python(String.raw` +def doh(host, path, query, timeout): + calls.append(host) + return reply(query, 0, 1) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=doh) +out = r.resolve(q("github.com", ident=9)) +print(dns.rcode(out), struct.unpack("!H", out[:2])[0], calls) +nodata = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda h, p, query, t: reply(query, 0, 0)) +print(dns.rcode(nodata.resolve(q("github.com", qtype=28)))) +`)).toEqual(["0 9 ['1.1.1.1']", "0"]); + }); + + test("a fallback NXDOMAIN or a total outage answers SERVFAIL, never a missing name", async () => { + expect(await python(String.raw` +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 2), doh_exchange=lambda h, p, query, t: reply(query, 3)) +out = r.resolve(q("coderouter.cmux.internal")) +print(dns.rcode(out), out[3] & 0x80 != 0, dns.question_key(out) == dns.question_key(q("coderouter.cmux.internal"))) +dead = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda *a: None) +print(dns.rcode(dead.resolve(q("github.com")))) +`)).toEqual(["2 True True", "2"]); + }); + + test("the platform's NXDOMAIN is an answer, not a failure", async () => { + expect(await python(String.raw` +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 3), doh_exchange=lambda *a: calls.append("doh")) +print(dns.rcode(r.resolve(q("missing.example"))), calls) +`)).toEqual(["3 []"]); + }); + + test("a new resolver set drops answers cached under the old one", async () => { + // A clone resumes with the snapshot's cache; when a TLS rule arrives the + // platform's answers for steered names change, and a stale one must not + // send traffic around the edge. + expect(await python(String.raw` +servers = ["8.8.8.8"] +state = {"down": False} +def exchange(server, query, timeout, tcp): + return None if state["down"] else reply(query, 0, 1) +r = dns.Resolver(upstreams=lambda: servers, exchange=exchange, doh_exchange=lambda *a: None) +r.resolve(q("api.openai.com")) +servers[:] = ["10.32.0.28"] +state["down"] = True +print(dns.rcode(r.resolve(q("api.openai.com")))) +`)).toEqual(["2"]); + }); + + test("the whole fallback chain stays inside one query budget", async () => { + expect(await python(String.raw` +now = [0.0] +def exchange(server, query, timeout, tcp): + now[0] += timeout + calls.append(round(timeout, 2)) +def doh(host, path, query, timeout): + now[0] += timeout + calls.append((host, round(timeout, 2))) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], clock=lambda: now[0], exchange=exchange, doh_exchange=doh) +r.resolve(q("github.com")) +print(calls, now[0] <= dns.QUERY_BUDGET) +`)).toEqual(["[1.0, 1.0, ('1.1.1.1', 1.5)] True"]); + }); + + test("serves real UDP and TCP clients and passes a truncated platform answer through", async () => { + expect(await python(String.raw` +import socket +def exchange(server, query, timeout, tcp): + return reply(query, 0, 1, tc=not tcp) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: None) +udp, tcp = dns.serve(r, host="127.0.0.1", port=0) +import threading; threading.Thread(target=udp.serve_forever, daemon=True).start() +c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); c.settimeout(5) +c.sendto(q("github.com"), udp.server_address); first = c.recv(4096) +print("udp", dns.rcode(first), first[2] & 0x02 != 0) +s = socket.create_connection(tcp.server_address, timeout=5) +s.sendall(struct.pack("!H", len(q("github.com"))) + q("github.com")); answer = dns.read_framed(s) +print("tcp", dns.rcode(answer), answer[2] & 0x02 != 0) +udp.shutdown(); tcp.shutdown() +`)).toEqual(["udp 0 True", "tcp 0 False"]); + }); +}); + +describe("cmux-dns guard", () => { + test("points the platform's resolv.conf at the stub and keeps its resolvers after it", async () => { + const directory = fixture(); + const conf = path.join(directory, "resolv.conf"); + writeFileSync(conf, "# freestyle-tls forwarding resolver\noptions timeout:1 attempts:2\nnameserver 10.32.0.28\n"); + expect(await python(String.raw` +print(dns.guard(sys.argv[2]), dns.guard(sys.argv[2])) +print(dns.upstreams_from(open(sys.argv[2]).read())) +`, conf)).toEqual(["True False", "['10.32.0.28']"]); + expect(readFileSync(conf, "utf8").split("\n").filter((line) => !line.startsWith("# ") || line.startsWith("# cmux-dns"))).toEqual([ + "# cmux-dns", + "# cmux-dns upstream 10.32.0.28", + "nameserver 127.0.0.2", + "nameserver 10.32.0.28", + "options timeout:4 attempts:2", + "", + ]); + }); + + test("records every base resolver though libc reads only three nameservers", async () => { + const directory = fixture(); + const conf = path.join(directory, "resolv.conf"); + writeFileSync(conf, "search corp.example\nnameserver 2606:4700:4700::1111\nnameserver 2001:4860:4860::8888\nnameserver 8.8.8.8\nnameserver 8.8.4.4\n"); + expect(await python(String.raw` +dns.guard(sys.argv[2]) +text = open(sys.argv[2]).read() +print(dns.upstreams_from(text)) +print(dns.nameservers(text)) +print([line for line in text.splitlines() if line.startswith("search")]) +`, conf)).toEqual([ + "['2606:4700:4700::1111', '2001:4860:4860::8888', '8.8.8.8', '8.8.4.4']", + "['127.0.0.2', '2606:4700:4700::1111', '2001:4860:4860::8888']", + "['search corp.example']", + ]); + }); + + test("leaves a file with no nameserver alone", async () => { + // A platform write caught half-way has no nameserver yet; its close + // triggers the guard again. + const directory = fixture(); + const conf = path.join(directory, "resolv.conf"); + writeFileSync(conf, "options timeout:1\n"); + expect(await python("print(dns.guard(sys.argv[2]))", conf)).toEqual(["False"]); + expect(readFileSync(conf, "utf8")).toBe("options timeout:1\n"); + }); +}); + +describe("cmux-dns image contract", () => { + test("the units run the distribution python and re-guard on every resolv.conf change", () => { + expect(Object.keys(DEVBOX_DNS_UNITS).sort()).toEqual([ + "/etc/systemd/system/cmux-dns-guard.path", + "/etc/systemd/system/cmux-dns-guard.service", + "/etc/systemd/system/cmux-dns.service", + ]); + expect(DEVBOX_DNS_UNITS["/etc/systemd/system/cmux-dns.service"]).toContain(`ExecStart=/usr/bin/python3 ${DEVBOX_DNS_PATH} serve`); + expect(DEVBOX_DNS_UNITS["/etc/systemd/system/cmux-dns-guard.path"]).toContain("PathChanged=/etc/resolv.conf"); + expect(devboxDnsCheckCommand).toContain("127.0.0.2"); + }); +}); diff --git a/web/tests/vm-devbox-image.test.ts b/web/tests/vm-devbox-image.test.ts index f2714311e06d..2304310a530c 100644 --- a/web/tests/vm-devbox-image.test.ts +++ b/web/tests/vm-devbox-image.test.ts @@ -121,6 +121,7 @@ describe("devbox image template", () => { "cmux-claude", "cmux-codex", "cmux-devbox-boot", + "cmux-dns", "cmux-motd", "cmux-opencode", "cmux-prompt-sync", From 9f6ea2a5d081ad91c75a5e1759c8ef1855a1a54e Mon Sep 17 00:00:00 2001 From: Austin Wang <38676809+austinywang@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:45:03 -0700 Subject: [PATCH 02/14] fix(cloud): guest DNS fallback for the platform resolver's SERVFAILs (#26640) Every cmux machine has TLS rules, so Freestyle points resolv.conf at its forwarding resolver (10.32.0.28) and intercepts all guest port-53 traffic. That forwarder intermittently answers SERVFAIL or nothing for every name on every machine at once; changing resolv.conf cannot route around it, and the guest agent rewrites the file on every TLS rule change and on resume. Bake a stdlib-only stub (cmux-dns) on 127.0.0.2:53: platform resolver first (steered names still resolve to the edge), then the last good platform answer, then DNS-over-HTTPS to 1.1.1.1/8.8.8.8 by IP, accepting only NOERROR. A path unit re-points resolv.conf at the stub whenever the platform rewrites it, keeping the platform's resolvers after it. Co-Authored-By: Claude Opus 5.5 --- web/scripts/build-devbox-freestyle.ts | 11 + web/scripts/devbox-image-common.ts | 77 +++++ web/scripts/verify-devbox-image.ts | 2 + web/services/vms/images/devbox/README.md | 45 +++ web/services/vms/images/devbox/cmux-dns | 344 +++++++++++++++++++++++ 5 files changed, 479 insertions(+) create mode 100644 web/services/vms/images/devbox/cmux-dns diff --git a/web/scripts/build-devbox-freestyle.ts b/web/scripts/build-devbox-freestyle.ts index ca2a19104a14..b37b698e8ad9 100644 --- a/web/scripts/build-devbox-freestyle.ts +++ b/web/scripts/build-devbox-freestyle.ts @@ -103,6 +103,10 @@ import { CMUX_TUI_STATE_RESERVATION_MARKER_PATH, CMUX_TUI_STATE_RESERVATION_BYTES, DEVBOX_DESKTOP_INSTALLS, + DEVBOX_DNS_PATH, + DEVBOX_DNS_UNITS, + devboxDnsCheckCommand, + devboxDnsInstallCommand, devboxTerminfoInstallCommand, DEVBOX_INSTANCE_ID_COMMAND, bakeMetadata, @@ -625,6 +629,13 @@ try { "cmux-prompt-sync-unit", "python3 -m py_compile /usr/local/bin/cmux-prompt-sync && systemctl daemon-reload && systemctl enable cmux-prompt-sync && systemctl is-enabled cmux-prompt-sync", ); + // The DNS fallback (devbox-image-common.ts, DEVBOX_DNS_UNITS): from here on + // the builder itself resolves through it, so the rest of the bake proves it. + await put("cmux-dns", DEVBOX_DNS_PATH, 0o755); + for (const [target, unit] of Object.entries(DEVBOX_DNS_UNITS)) { + await vm.fs.writeFile(target, unit, { mode: 0o644 }); + } + await step("dns-fallback", `${devboxDnsInstallCommand} && ${devboxDnsCheckCommand}`); // Prove the daemon contract on the builder: the supervisor started the // daemon on its own, the session answers, and the listener is dual-stack. await step( diff --git a/web/scripts/devbox-image-common.ts b/web/scripts/devbox-image-common.ts index 66f053db2406..7078a9f9bc7f 100644 --- a/web/scripts/devbox-image-common.ts +++ b/web/scripts/devbox-image-common.ts @@ -1564,3 +1564,80 @@ export const DEVBOX_PRE_SNAPSHOT_SETTLE_SECONDS = 10; export function devboxSettleBeforeSnapshotCommand(): string { return `sync && sleep ${DEVBOX_PRE_SNAPSHOT_SETTLE_SECONDS} && sync && echo "settled $(cut -d' ' -f1-3 /proc/loadavg)"`; } + +/** + * The DNS fallback (Freestyle only; services/vms/images/devbox/cmux-dns): a + * stub on 127.0.0.2:53 that asks the platform's resolver first and falls back + * to its last good answer, then DNS-over-HTTPS, when the platform resolver + * answers SERVFAIL or nothing (cmux issue 26640). Freestyle rewrites + * /etc/resolv.conf on every TLS rule change, so a path unit re-points it at + * the stub each time. The container recipe has none: its runtime owns + * resolv.conf. + */ +export const DEVBOX_DNS_PATH = "/usr/local/bin/cmux-dns"; +export const DEVBOX_DNS_UNITS: Readonly> = { + "/etc/systemd/system/cmux-dns.service": [ + "[Unit]", + "Description=cmux DNS fallback stub (127.0.0.2:53)", + "Before=nss-lookup.target", + "Wants=nss-lookup.target", + "", + "[Service]", + "Type=simple", + // The distribution interpreter, not whichever python3 is first on PATH. + `ExecStart=/usr/bin/python3 ${DEVBOX_DNS_PATH} serve`, + "Restart=always", + "RestartSec=1", + "DynamicUser=yes", + "AmbientCapabilities=CAP_NET_BIND_SERVICE", + "CapabilityBoundingSet=CAP_NET_BIND_SERVICE", + "", + "[Install]", + "WantedBy=multi-user.target", + ].join("\n") + "\n", + "/etc/systemd/system/cmux-dns-guard.service": [ + "[Unit]", + "Description=Point resolv.conf at the cmux DNS fallback", + "After=cmux-dns.service", + "", + "[Service]", + "Type=oneshot", + `ExecStart=/usr/bin/python3 ${DEVBOX_DNS_PATH} guard`, + "", + "[Install]", + "WantedBy=multi-user.target", + ].join("\n") + "\n", + "/etc/systemd/system/cmux-dns-guard.path": [ + "[Unit]", + "Description=Re-point resolv.conf at the cmux DNS fallback whenever the platform rewrites it", + "", + "[Path]", + "PathChanged=/etc/resolv.conf", + "Unit=cmux-dns-guard.service", + "", + "[Install]", + "WantedBy=multi-user.target", + ].join("\n") + "\n", +}; + +/** Enables and starts the fallback once its files are in place, then proves it answers. */ +export const devboxDnsInstallCommand = [ + `python3 -m py_compile ${DEVBOX_DNS_PATH}`, + "systemctl daemon-reload", + "systemctl enable --now cmux-dns.service cmux-dns-guard.path", + "systemctl enable cmux-dns-guard.service", + "systemctl start cmux-dns-guard.service", + "for i in $(seq 1 20); do getent ahosts github.com >/dev/null && break; sleep 0.5; done", +].join(" && "); + +/** The fallback is installed, serving, and resolv.conf points at it first. */ +export const devboxDnsCheckCommand = [ + `test -x ${DEVBOX_DNS_PATH}`, + `python3 -m py_compile ${DEVBOX_DNS_PATH}`, + "systemctl is-active cmux-dns.service >/dev/null", + "systemctl is-active cmux-dns-guard.path >/dev/null", + "head -1 /etc/resolv.conf | grep -qx '# cmux-dns'", + "[ \"$(awk '$1 == \"nameserver\" { print $2; exit }' /etc/resolv.conf)\" = 127.0.0.2 ]", + "getent ahostsv4 github.com >/dev/null", + "echo dns-fallback-ok", +].join(" && "); diff --git a/web/scripts/verify-devbox-image.ts b/web/scripts/verify-devbox-image.ts index 403c407320bd..9fa6474cff9f 100644 --- a/web/scripts/verify-devbox-image.ts +++ b/web/scripts/verify-devbox-image.ts @@ -54,6 +54,7 @@ import { devboxTerminfoCheckCommand, devboxWaitForDaemonCommand, cmuxTuiWebsocketSmokeCommand, + devboxDnsCheckCommand, sha256File, } from "./devbox-image-common"; import { @@ -175,6 +176,7 @@ const DAEMON_CHECKS: readonly string[] = [ `test -s /etc/cmux/model-plane.env && grep -q "^export OPENAI_BASE_URL='https://" /etc/cmux/model-plane.env && ! grep -q crt_ /etc/cmux/model-plane.env && env -i HOME=/tmp/mp-verify bash -c '. /etc/cmux/agent-config.sh; printf %s "$OPENAI_BASE_URL"' | grep -q '^https://' && rm -rf /tmp/mp-verify && echo model-plane-env-baked`, "systemctl is-active cmux-tui-daemon >/dev/null && echo systemd-supervisor-active", "test -x /usr/local/bin/cmux-prompt-sync && python3 -m py_compile /usr/local/bin/cmux-prompt-sync && systemctl is-enabled cmux-prompt-sync >/dev/null && echo prompt-sync-contract-ok", + devboxDnsCheckCommand, cmuxTuiWebsocketSmokeCommand(), ]; diff --git a/web/services/vms/images/devbox/README.md b/web/services/vms/images/devbox/README.md index f6e4f0ddb48c..2a822f2f6609 100644 --- a/web/services/vms/images/devbox/README.md +++ b/web/services/vms/images/devbox/README.md @@ -320,6 +320,51 @@ alone. The container recipe has no identity step: `docker build` mounts `/etc/hostname` and `/etc/hosts` from the daemon and the runtime names each container, so the hostname there is the runtime's. +## DNS fallback (`cmux-dns`) + +Freestyle only. A machine with any TLS rule (every cmux machine: the +model-plane and CodeRouter edge rules) resolves through the platform's +forwarding resolver, `10.32.0.28`: the guest agent writes +`/etc/resolv.conf` (`nameserver 10.32.0.28`) and +`/etc/systemd/resolved.conf.d/61-freestyle-tls.conf`, and the platform +answers every port-53 packet the guest sends, whatever address it names +(a query to `8.8.8.8` comes back from the same forwarder). The forwarder +answers the names TLS rules steer with the edge address and forwards the +rest. It intermittently answers SERVFAIL (about 300 ms, then cached for a +second or two) or nothing at all, for every name, on every machine at the +same moment ([#26640](https://github.com/manaflow-ai/cmux/issues/26640)); +measured 2026-10-28, roughly 2% of queries over a few minutes. Pointing +resolv.conf elsewhere does not help, and the agent rewrites it on every TLS +rule change and on resume anyway. + +`cmux-dns serve` (`cmux-dns.service`, a `DynamicUser` with only +`CAP_NET_BIND_SERVICE`) listens on `127.0.0.2:53`, UDP and TCP, and per +query: + +1. asks the platform's resolvers (a lone resolver twice, 1 s each), so + steered names keep resolving to the edge and NXDOMAIN stays the + platform's; +2. on SERVFAIL, REFUSED or silence, answers with the platform's last good + answer for that question (dropped when the resolver set changes, so a + snapshot's cache never outlives a new TLS rule); +3. otherwise asks DNS-over-HTTPS at `1.1.1.1` then `8.8.8.8`, addressed by IP + on 443, which the interception does not touch, and accepts only NOERROR: + a public NXDOMAIN for a name only the platform knows comes back as + SERVFAIL, never as a missing name. + +The whole chain fits in 3.5 s, under resolv.conf's 4 s per-server timeout. +`cmux-dns guard` (`cmux-dns-guard.path` on `PathChanged=/etc/resolv.conf`, +plus once at boot) rewrites a resolv.conf the platform wrote to +`nameserver 127.0.0.2` followed by the platform's first two resolvers, and +records all of them on a `# cmux-dns upstream` line the stub reads. A stopped +stub therefore costs one refused packet before libc asks the platform +directly. Between a platform rewrite and the guard (under a second) the +machine resolves the old way. The bake installs it after the prompt sync, so +every later bake step resolves through it, and `verify-devbox-image.ts` +checks the contract. The container recipe has none: Docker owns its +resolv.conf. Machines baked before it keep resolving straight through the +platform. + ## Terminal capabilities (`cmux-terminfo.src`) Daemon-spawned shells get the TERM the Mac exports (`xterm-256color`, or diff --git a/web/services/vms/images/devbox/cmux-dns b/web/services/vms/images/devbox/cmux-dns new file mode 100644 index 000000000000..1505588c187a --- /dev/null +++ b/web/services/vms/images/devbox/cmux-dns @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +"""Keep name resolution working when the platform resolver fails. + +A Freestyle machine with any TLS rule (every cmux machine has them) gets +`nameserver 10.32.0.28`, the platform's forwarding resolver, and the platform +answers every port-53 packet the guest sends, whatever address it names. That +resolver intermittently answers SERVFAIL, or nothing, for every name at once, +on every machine at the same moment (cmux issue 26640), and a changed +resolv.conf does not route around it. Freestyle also rewrites resolv.conf on +every TLS rule change. + +`serve` is a stub on 127.0.0.2:53 that asks the platform's resolvers first, so +names the platform steers to its edge keep resolving to the edge. When they +fail it answers from the last good platform answer for the name, then over +DNS-over-HTTPS to resolvers addressed by IP on 443, which the interception +does not touch. It never turns a platform failure into NXDOMAIN. + +`guard` (run by cmux-dns-guard.path whenever resolv.conf changes) points +resolv.conf at the stub and keeps the platform's resolvers after it, so a +stopped stub costs one refused packet, not resolution. +""" +import collections +import http.client +import os +import socket +import socketserver +import ssl +import struct +import sys +import tempfile +import threading +import time + +LISTEN = "127.0.0.2" +RESOLV_CONF = "/etc/resolv.conf" +MARKER = "# cmux-dns" +UPSTREAM_PREFIX = "# cmux-dns upstream " +# Freestyle's base resolvers (/etc/systemd/resolved.conf.d/60-freestyle-vms.conf). +DEFAULT_UPSTREAMS = ["2606:4700:4700::1111", "2001:4860:4860::8888", "8.8.8.8", "8.8.4.4"] +DOH_ENDPOINTS = [("1.1.1.1", "/dns-query"), ("8.8.8.8", "/dns-query")] +# One query's whole budget stays under resolv.conf's per-server timeout, so +# libc never retries a question the stub is still answering. +QUERY_BUDGET = 3.5 +UPSTREAM_TIMEOUT = 1.0 +DOH_TIMEOUT = 1.5 +CACHE_ENTRIES = 8192 +UDP_ANSWER_LIMIT = 1232 +SERVFAIL, REFUSED = 2, 5 + + +def question_key(message): + """The lowercased question (name, type, class), or None for a malformed query.""" + if len(message) < 12 or struct.unpack("!H", message[4:6])[0] != 1: + return None + offset, labels = 12, [] + while True: + if offset >= len(message): + return None + length = message[offset] + if length == 0: + offset += 1 + break + if length & 0xC0: + return None + labels.append(message[offset + 1:offset + 1 + length].lower()) + offset += 1 + length + if offset + 4 > len(message): + return None + return b".".join(labels) + message[offset:offset + 4] + + +def rcode(message): + return message[3] & 0x0F if len(message) >= 12 else SERVFAIL + + +def with_id(message, query): + return query[:2] + message[2:] + + +def failure(query): + """SERVFAIL for `query`: its header and question, with QR and RA set.""" + end = 12 + while end < len(query) and query[end] != 0: + end += 1 + query[end] + end = min(end + 5, len(query)) + flags = (query[2] & 0x79) | 0x80, 0x80 | SERVFAIL + return query[:2] + bytes(flags) + b"\x00\x01\x00\x00\x00\x00\x00\x00" + query[12:end] + + +def truncated(message, query): + """An answer too large for UDP: the header with TC set and no records.""" + head = failure(query) + return head[:2] + bytes([message[2] | 0x02, message[3]]) + head[4:] + + +def usable(message): + return message is not None and len(message) >= 12 and rcode(message) not in (SERVFAIL, REFUSED) + + +def found(message): + """NOERROR: the name exists, with or without records of the asked type.""" + return usable(message) and rcode(message) == 0 + + +def read_resolv_conf(path=RESOLV_CONF): + try: + with open(path) as stream: + return stream.read() + except OSError: + return "" + + +def nameservers(text): + found = [] + for line in text.splitlines(): + fields = line.split() + if len(fields) >= 2 and fields[0] == "nameserver": + found.append(fields[1]) + return found + + +def upstreams_from(text): + """The platform's resolvers: the guard's record of them, else the file's own servers.""" + for line in text.splitlines(): + if line.startswith(UPSTREAM_PREFIX): + servers = line[len(UPSTREAM_PREFIX):].split() + if servers: + return servers + servers = [server for server in nameservers(text) if server != LISTEN] + return servers or list(DEFAULT_UPSTREAMS) + + +def guarded(text): + """resolv.conf pointing at the stub, or None when `text` needs no change.""" + if text.startswith(MARKER + "\n") or not nameservers(text): + return None + servers = [server for server in nameservers(text) if server != LISTEN] + if not servers: + return None + kept = [line for line in text.splitlines() if line.split()[:1] in (["search"], ["domain"])] + # libc reads at most three nameservers: the stub plus the first two upstreams. + lines = [ + MARKER, + "# The platform's resolvers follow the stub; cmux-dns rewrites this file", + "# whenever the platform does (see /usr/local/bin/cmux-dns).", + UPSTREAM_PREFIX + " ".join(servers), + *kept, + f"nameserver {LISTEN}", + *[f"nameserver {server}" for server in servers[:2]], + "options timeout:4 attempts:2", + ] + return "\n".join(lines) + "\n" + + +def guard(path=RESOLV_CONF): + text = read_resolv_conf(path) + updated = guarded(text) + if updated is None: + return False + directory = os.path.dirname(path) or "." + fd, temporary = tempfile.mkstemp(prefix=".resolv.conf.cmux-", dir=directory) + try: + with os.fdopen(fd, "w") as stream: + stream.write(updated) + os.fchmod(stream.fileno(), 0o644) + os.replace(temporary, path) + finally: + if os.path.lexists(temporary): + os.unlink(temporary) + return True + + +class Resolver: + """Platform first, then the last good platform answer, then DNS-over-HTTPS.""" + + def __init__(self, upstreams=None, doh=None, clock=time.monotonic, exchange=None, doh_exchange=None): + self._upstreams = upstreams or (lambda: upstreams_from(read_resolv_conf())) + self._doh = DOH_ENDPOINTS if doh is None else doh + self._clock = clock + self._exchange = exchange or exchange_dns + self._doh_exchange = doh_exchange or exchange_doh + self._cache = collections.OrderedDict() + self._cache_for = None + self._lock = threading.Lock() + + def _cached(self, servers, key, answer=None): + with self._lock: + # A clone resumes with the snapshot's cache; another resolver set + # (a TLS rule arrived) means those answers are not the platform's now. + if self._cache_for != servers: + self._cache.clear() + self._cache_for = servers + if answer is not None: + self._cache[key] = answer + self._cache.move_to_end(key) + while len(self._cache) > CACHE_ENTRIES: + self._cache.popitem(last=False) + return answer + hit = self._cache.get(key) + if hit is not None: + self._cache.move_to_end(key) + return hit + + def resolve(self, query, tcp=False): + key = question_key(query) + if key is None: + return failure(query) if len(query) >= 12 else None + deadline = self._clock() + QUERY_BUDGET + servers = tuple(self._upstreams()) + # A lone upstream gets a second try: most failures last under a second. + attempts = list(servers) if len(servers) > 1 else list(servers) * 2 + for server in attempts: + remaining = deadline - self._clock() + if remaining <= 0: + break + reply = self._exchange(server, query, min(UPSTREAM_TIMEOUT, remaining), tcp) + if usable(reply): + if reply[2] & 0x02 == 0: + self._cached(servers, key, reply) + return reply + stale = self._cached(servers, key) + if stale is not None: + return with_id(stale, query) + for host, path in self._doh: + remaining = deadline - self._clock() + if remaining <= 0: + break + reply = self._doh_exchange(host, path, query, min(DOH_TIMEOUT, remaining)) + # Never NXDOMAIN: public resolvers do not know the names only the + # platform answers, and an outage must not look like a missing name. + if found(reply): + return with_id(reply, query) + return failure(query) + + +def exchange_dns(server, query, timeout, tcp): + family = socket.AF_INET6 if ":" in server else socket.AF_INET + try: + if tcp: + with socket.socket(family, socket.SOCK_STREAM) as stream: + stream.settimeout(timeout) + stream.connect((server, 53)) + stream.sendall(struct.pack("!H", len(query)) + query) + return read_framed(stream) + with socket.socket(family, socket.SOCK_DGRAM) as datagram: + datagram.settimeout(timeout) + datagram.connect((server, 53)) + datagram.send(query) + end = time.monotonic() + timeout + while True: + reply = datagram.recv(65535) + if reply[:2] == query[:2]: + return reply + datagram.settimeout(max(end - time.monotonic(), 0.001)) + except OSError: + return None + + +_TLS = ssl.create_default_context() + + +def exchange_doh(host, path, query, timeout): + """RFC 8484 POST; the certificate must cover the IP address itself.""" + connection = http.client.HTTPSConnection(host, 443, timeout=timeout, context=_TLS) + try: + connection.request("POST", path, body=b"\x00\x00" + query[2:], headers={ + "Content-Type": "application/dns-message", + "Accept": "application/dns-message", + }) + response = connection.getresponse() + body = response.read(65536) + return body if response.status == 200 else None + except (OSError, http.client.HTTPException): + return None + finally: + connection.close() + + +def read_exact(stream, count): + data = b"" + while len(data) < count: + chunk = stream.recv(count - len(data)) + if not chunk: + raise ConnectionError("short read") + data += chunk + return data + + +def read_framed(stream): + return read_exact(stream, struct.unpack("!H", read_exact(stream, 2))[0]) + + +def serve(resolver, host=LISTEN, port=53): + class Datagram(socketserver.BaseRequestHandler): + def handle(self): + query, sock = self.request + reply = resolver.resolve(query) + if reply is None: + return + if len(reply) > UDP_ANSWER_LIMIT and len(reply) > 512: + reply = truncated(reply, query) + sock.sendto(reply, self.client_address) + + class Stream(socketserver.BaseRequestHandler): + def handle(self): + self.request.settimeout(10) + try: + while True: + query = read_framed(self.request) + reply = resolver.resolve(query, tcp=True) + if reply is None: + return + self.request.sendall(struct.pack("!H", len(reply)) + reply) + except (OSError, ConnectionError): + return + + class UDPServer(socketserver.ThreadingMixIn, socketserver.UDPServer): + daemon_threads = True + + class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + daemon_threads = True + allow_reuse_address = True + + udp = UDPServer((host, port), Datagram) + tcp = TCPServer((host, port), Stream) + threading.Thread(target=tcp.serve_forever, daemon=True).start() + return udp, tcp + + +def main(argv): + command = argv[1] if len(argv) > 1 else "" + if command == "guard": + print("resolv.conf points at cmux-dns" if guard() or read_resolv_conf().startswith(MARKER + "\n") else "resolv.conf unchanged") + return 0 + if command == "serve": + udp, _ = serve(Resolver()) + udp.serve_forever() + return 0 + print("usage: cmux-dns serve|guard", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) From 2120cc6d1bb7e377d9c25d69cddccdca326c459d Mon Sep 17 00:00:00 2001 From: Austin Wang <38676809+austinywang@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:59:47 -0700 Subject: [PATCH 03/14] fix(cloud): address review of the guest DNS fallback (#26640) - cache key is the wire question, so label boundaries cannot collide - stale answers expire after a day and start over on every resolv.conf write (TLS rule, resume), not only when the resolver list changes - every exchange runs under a wall-clock deadline; two platform tries at most, so DoH keeps its budget - UDP truncation follows the client's size (512 or EDNS up to 1232) - replies for another question are dropped; responses and queries without exactly one question get no answer - guard re-reads before replacing, keeps search and other options, lists IPv4 fallbacks first, and sets attempts:1 (the stub retries itself) - bake and verifier probe the stub directly (libc would hide a dead one) - driver comments: steering is now DNS-based, not /etc/hosts Co-Authored-By: Claude Opus 5.5 --- web/scripts/devbox-image-common.ts | 9 +- web/services/vms/drivers/freestyle.ts | 3 +- .../vms/drivers/freestyleNetworkPolicy.ts | 7 +- web/services/vms/images/devbox/README.md | 41 ++-- web/services/vms/images/devbox/cmux-dns | 199 ++++++++++++------ web/services/vms/networkPolicy.ts | 5 +- web/tests/vm-devbox-dns.test.ts | 95 ++++++++- 7 files changed, 266 insertions(+), 93 deletions(-) diff --git a/web/scripts/devbox-image-common.ts b/web/scripts/devbox-image-common.ts index 7078a9f9bc7f..275b6cefe134 100644 --- a/web/scripts/devbox-image-common.ts +++ b/web/scripts/devbox-image-common.ts @@ -1620,17 +1620,19 @@ export const DEVBOX_DNS_UNITS: Readonly> = { ].join("\n") + "\n", }; -/** Enables and starts the fallback once its files are in place, then proves it answers. */ +/** Enables and starts the fallback once its files are in place, then waits until the stub answers. */ export const devboxDnsInstallCommand = [ `python3 -m py_compile ${DEVBOX_DNS_PATH}`, "systemctl daemon-reload", "systemctl enable --now cmux-dns.service cmux-dns-guard.path", "systemctl enable cmux-dns-guard.service", "systemctl start cmux-dns-guard.service", - "for i in $(seq 1 20); do getent ahosts github.com >/dev/null && break; sleep 0.5; done", + // The stub itself must answer: libc would hide a dead one behind the + // platform resolvers listed after it. + `{ for i in $(seq 1 20); do /usr/bin/python3 ${DEVBOX_DNS_PATH} probe && break; sleep 0.5; done; /usr/bin/python3 ${DEVBOX_DNS_PATH} probe; }`, ].join(" && "); -/** The fallback is installed, serving, and resolv.conf points at it first. */ +/** The fallback is installed, answers on its own, and resolv.conf points at it first. */ export const devboxDnsCheckCommand = [ `test -x ${DEVBOX_DNS_PATH}`, `python3 -m py_compile ${DEVBOX_DNS_PATH}`, @@ -1638,6 +1640,7 @@ export const devboxDnsCheckCommand = [ "systemctl is-active cmux-dns-guard.path >/dev/null", "head -1 /etc/resolv.conf | grep -qx '# cmux-dns'", "[ \"$(awk '$1 == \"nameserver\" { print $2; exit }' /etc/resolv.conf)\" = 127.0.0.2 ]", + `/usr/bin/python3 ${DEVBOX_DNS_PATH} probe github.com`, "getent ahostsv4 github.com >/dev/null", "echo dns-fallback-ok", ].join(" && "); diff --git a/web/services/vms/drivers/freestyle.ts b/web/services/vms/drivers/freestyle.ts index 22f6415c7296..ff9aca32ea29 100644 --- a/web/services/vms/drivers/freestyle.ts +++ b/web/services/vms/drivers/freestyle.ts @@ -154,7 +154,8 @@ export type { FreestylePreconnectOptions } from "./freestyleWarmup"; // The coderouter model plane is edge-injected: the create carries an inline // `tls` rule for the coderouter host whose transform overwrites the signed // `x-cmux-authorization` plus compatibility bearer/VM headers on every request -// the guest makes there. The platform steers the host to its edge (/etc/hosts) and +// the guest makes there. The platform steers the host to its edge (its forwarding +// resolver answers it with the edge address; see the devbox README) and // installs its CA at boot; rules added after boot never reach a running // guest, so the rule must be inline. The baked env file holds only base // URLs and placeholder keys: no token is ever written into the guest, and diff --git a/web/services/vms/drivers/freestyleNetworkPolicy.ts b/web/services/vms/drivers/freestyleNetworkPolicy.ts index 430691f773ce..6288b0108fee 100644 --- a/web/services/vms/drivers/freestyleNetworkPolicy.ts +++ b/web/services/vms/drivers/freestyleNetworkPolicy.ts @@ -9,8 +9,11 @@ import { canonicalCidr, type NetworkRulePlan } from "../networkPolicy"; * - A VM with no egress rule reaches nothing, DNS included: the guest's * resolvers are public addresses. * - Firewall and TLS rule changes apply to a running VM within ~0.1 s. - * - A TLS rule with a `public` destination steers its exact name through the - * guest's /etc/hosts to the edge and needs no firewall grant. + * - A TLS rule with a `public` destination steers its exact name to the edge + * and needs no firewall grant. Steering was through the guest's /etc/hosts + * at the spike; by 2026-10-28 the platform instead points resolv.conf at a + * forwarding resolver on the edge address that answers steered names with + * the edge (see the devbox README, "DNS fallback"). * - A TLS rule with a `host` destination (the CodeRouter and reflection * aliases) does NOT carry that grant: the guest needs a firewall path to the * edge address on 443. {@link freestyleEdgeAddresses} supplies it. diff --git a/web/services/vms/images/devbox/README.md b/web/services/vms/images/devbox/README.md index 2a822f2f6609..17df51db3138 100644 --- a/web/services/vms/images/devbox/README.md +++ b/web/services/vms/images/devbox/README.md @@ -341,29 +341,38 @@ rule change and on resume anyway. `CAP_NET_BIND_SERVICE`) listens on `127.0.0.2:53`, UDP and TCP, and per query: -1. asks the platform's resolvers (a lone resolver twice, 1 s each), so - steered names keep resolving to the edge and NXDOMAIN stays the - platform's; +1. asks the platform's resolvers (two tries of 1 s: the first two resolvers, + or a lone one twice), so steered names keep resolving to the edge and + NXDOMAIN stays the platform's; 2. on SERVFAIL, REFUSED or silence, answers with the platform's last good - answer for that question (dropped when the resolver set changes, so a - snapshot's cache never outlives a new TLS rule); + answer for that exact question, at most a day old. The cache starts over + whenever resolv.conf is written (a TLS rule, a resume, a clone's first + boot), so a stale answer never outlives a steering change; 3. otherwise asks DNS-over-HTTPS at `1.1.1.1` then `8.8.8.8`, addressed by IP on 443, which the interception does not touch, and accepts only NOERROR: a public NXDOMAIN for a name only the platform knows comes back as - SERVFAIL, never as a missing name. + SERVFAIL, never as a missing name. A machine whose network policy blocks + those addresses gets SERVFAIL from this step, as it would without the stub. + +Each exchange runs under a wall-clock deadline and the whole chain fits in +3.5 s, under the stub's 4 s libc timeout. Replies for another question are +dropped; UDP replies over the client's size (512, or its EDNS size up to +1232) go out truncated so the client retries over TCP. -The whole chain fits in 3.5 s, under resolv.conf's 4 s per-server timeout. `cmux-dns guard` (`cmux-dns-guard.path` on `PathChanged=/etc/resolv.conf`, plus once at boot) rewrites a resolv.conf the platform wrote to -`nameserver 127.0.0.2` followed by the platform's first two resolvers, and -records all of them on a `# cmux-dns upstream` line the stub reads. A stopped -stub therefore costs one refused packet before libc asks the platform -directly. Between a platform rewrite and the guard (under a second) the -machine resolves the old way. The bake installs it after the prompt sync, so -every later bake step resolves through it, and `verify-devbox-image.ts` -checks the contract. The container recipe has none: Docker owns its -resolv.conf. Machines baked before it keep resolving straight through the -platform. +`nameserver 127.0.0.2` followed by two of the platform's resolvers (IPv4 +first), keeps its `search` line and options other than `timeout`/`attempts` +(set to `timeout:4 attempts:1`; the stub retries itself), and records every +resolver on a `# cmux-dns upstream` line the stub reads. A stopped stub +therefore costs one refused packet before libc asks the platform directly. +Between a platform rewrite and the guard (under a second) the machine +resolves the old way. The bake installs it after the prompt sync and waits +until the stub itself answers (`cmux-dns probe`; a libc lookup would pass +through the fallback resolvers), every later bake step resolves through it, +and `verify-devbox-image.ts` checks the same contract. The container recipe +has none: Docker owns its resolv.conf. Machines baked before it keep +resolving straight through the platform. ## Terminal capabilities (`cmux-terminfo.src`) diff --git a/web/services/vms/images/devbox/cmux-dns b/web/services/vms/images/devbox/cmux-dns index 1505588c187a..105057af1d7d 100644 --- a/web/services/vms/images/devbox/cmux-dns +++ b/web/services/vms/images/devbox/cmux-dns @@ -7,11 +7,12 @@ answers every port-53 packet the guest sends, whatever address it names. That resolver intermittently answers SERVFAIL, or nothing, for every name at once, on every machine at the same moment (cmux issue 26640), and a changed resolv.conf does not route around it. Freestyle also rewrites resolv.conf on -every TLS rule change. +every TLS rule change and on resume. `serve` is a stub on 127.0.0.2:53 that asks the platform's resolvers first, so names the platform steers to its edge keep resolving to the edge. When they -fail it answers from the last good platform answer for the name, then over +fail it answers from the last good platform answer for the question (at most +a day old, and only since the platform last wrote resolv.conf), then over DNS-over-HTTPS to resolvers addressed by IP on 443, which the interception does not touch. It never turns a platform failure into NXDOMAIN. @@ -20,6 +21,7 @@ resolv.conf at the stub and keeps the platform's resolvers after it, so a stopped stub costs one refused packet, not resolution. """ import collections +import concurrent.futures import http.client import os import socket @@ -38,21 +40,26 @@ UPSTREAM_PREFIX = "# cmux-dns upstream " # Freestyle's base resolvers (/etc/systemd/resolved.conf.d/60-freestyle-vms.conf). DEFAULT_UPSTREAMS = ["2606:4700:4700::1111", "2001:4860:4860::8888", "8.8.8.8", "8.8.4.4"] DOH_ENDPOINTS = [("1.1.1.1", "/dns-query"), ("8.8.8.8", "/dns-query")] -# One query's whole budget stays under resolv.conf's per-server timeout, so -# libc never retries a question the stub is still answering. +# One query's whole budget stays under resolv.conf's timeout for the stub, so +# libc never gives up on a question the stub is still answering. Two platform +# tries leave DoH at least 1.5 s however many resolvers the platform lists. QUERY_BUDGET = 3.5 +UPSTREAM_TRIES = 2 UPSTREAM_TIMEOUT = 1.0 DOH_TIMEOUT = 1.5 +RESOLV_OPTIONS = "timeout:4 attempts:1" CACHE_ENTRIES = 8192 +STALE_MAX_AGE = 24 * 3600 UDP_ANSWER_LIMIT = 1232 SERVFAIL, REFUSED = 2, 5 +OPT = 41 -def question_key(message): - """The lowercased question (name, type, class), or None for a malformed query.""" - if len(message) < 12 or struct.unpack("!H", message[4:6])[0] != 1: +def question_end(message): + """Offset just past the single question, or None for anything but one well-formed query.""" + if len(message) < 12 or message[2] & 0x80 or struct.unpack("!H", message[4:6])[0] != 1: return None - offset, labels = 12, [] + offset = 12 while True: if offset >= len(message): return None @@ -62,11 +69,25 @@ def question_key(message): break if length & 0xC0: return None - labels.append(message[offset + 1:offset + 1 + length].lower()) offset += 1 + length - if offset + 4 > len(message): - return None - return b".".join(labels) + message[offset:offset + 4] + return offset + 4 if offset + 4 <= len(message) else None + + +def question_key(message): + """The question as wire bytes (length-prefixed labels, lowercased), type and class.""" + end = question_end(message) + return None if end is None else message[12:end].lower() + + +def udp_limit(query): + """The largest UDP reply the client accepts: 512, or its EDNS size up to 1232.""" + end = question_end(query) + if end is None or struct.unpack("!H", query[10:12])[0] == 0: + return 512 + # The OPT record a stub resolver sends: root name, type 41, class = UDP size. + if query[end:end + 3] == b"\x00\x00\x29" and len(query) >= end + 5: + return max(512, min(struct.unpack("!H", query[end + 3:end + 5])[0], UDP_ANSWER_LIMIT)) + return 512 def rcode(message): @@ -78,17 +99,14 @@ def with_id(message, query): def failure(query): - """SERVFAIL for `query`: its header and question, with QR and RA set.""" - end = 12 - while end < len(query) and query[end] != 0: - end += 1 + query[end] - end = min(end + 5, len(query)) + """SERVFAIL for a well-formed `query`: its header and question, with QR and RA set.""" + end = question_end(query) flags = (query[2] & 0x79) | 0x80, 0x80 | SERVFAIL return query[:2] + bytes(flags) + b"\x00\x01\x00\x00\x00\x00\x00\x00" + query[12:end] def truncated(message, query): - """An answer too large for UDP: the header with TC set and no records.""" + """A reply too large for the client's UDP limit: its header with TC set, no records.""" head = failure(query) return head[:2] + bytes([message[2] | 0x02, message[3]]) + head[4:] @@ -102,6 +120,12 @@ def found(message): return usable(message) and rcode(message) == 0 +def answers(message, query): + """`message` replies to `query`'s question (resolvers echo it; anything else is discarded).""" + end = question_end(query) + return message is not None and len(message) >= end and message[12:end].lower() == query[12:end].lower() + + def read_resolv_conf(path=RESOLV_CONF): try: with open(path) as stream: @@ -111,12 +135,12 @@ def read_resolv_conf(path=RESOLV_CONF): def nameservers(text): - found = [] + found_servers = [] for line in text.splitlines(): fields = line.split() if len(fields) >= 2 and fields[0] == "nameserver": - found.append(fields[1]) - return found + found_servers.append(fields[1]) + return found_servers def upstreams_from(text): @@ -132,13 +156,20 @@ def upstreams_from(text): def guarded(text): """resolv.conf pointing at the stub, or None when `text` needs no change.""" - if text.startswith(MARKER + "\n") or not nameservers(text): + if text.startswith(MARKER + "\n"): return None servers = [server for server in nameservers(text) if server != LISTEN] if not servers: return None kept = [line for line in text.splitlines() if line.split()[:1] in (["search"], ["domain"])] - # libc reads at most three nameservers: the stub plus the first two upstreams. + options = [ + option + for line in text.splitlines() if line.split()[:1] == ["options"] + for option in line.split()[1:] if not option.startswith(("timeout:", "attempts:")) + ] + # libc reads at most three nameservers: the stub plus two upstreams, IPv4 + # first because every machine routes IPv4 and not every one routes IPv6. + fallback = sorted(servers, key=lambda server: ":" in server)[:2] lines = [ MARKER, "# The platform's resolvers follow the stub; cmux-dns rewrites this file", @@ -146,35 +177,65 @@ def guarded(text): UPSTREAM_PREFIX + " ".join(servers), *kept, f"nameserver {LISTEN}", - *[f"nameserver {server}" for server in servers[:2]], - "options timeout:4 attempts:2", + *[f"nameserver {server}" for server in fallback], + " ".join(["options", RESOLV_OPTIONS, *options]), ] return "\n".join(lines) + "\n" def guard(path=RESOLV_CONF): - text = read_resolv_conf(path) - updated = guarded(text) - if updated is None: - return False - directory = os.path.dirname(path) or "." - fd, temporary = tempfile.mkstemp(prefix=".resolv.conf.cmux-", dir=directory) + for _ in range(5): + text = read_resolv_conf(path) + updated = guarded(text) + if updated is None: + return False + directory = os.path.dirname(path) or "." + fd, temporary = tempfile.mkstemp(prefix=".resolv.conf.cmux-", dir=directory) + try: + with os.fdopen(fd, "w") as stream: + stream.write(updated) + os.fchmod(stream.fileno(), 0o644) + # The platform may have written again since the read: guard that + # version instead of replacing it with a copy of the old one. + if read_resolv_conf(path) != text: + continue + os.replace(temporary, path) + return True + finally: + if os.path.lexists(temporary): + os.unlink(temporary) + return False + + +def resolv_conf_source(path=RESOLV_CONF): + """The platform's resolvers and a generation that changes whenever resolv.conf is written.""" try: - with os.fdopen(fd, "w") as stream: - stream.write(updated) - os.fchmod(stream.fileno(), 0o644) - os.replace(temporary, path) - finally: - if os.path.lexists(temporary): - os.unlink(temporary) - return True + status = os.stat(path) + generation = (status.st_ino, status.st_mtime_ns) + except OSError: + generation = None + return generation, upstreams_from(read_resolv_conf(path)) + + +_POOL = concurrent.futures.ThreadPoolExecutor(max_workers=64) + + +def bounded(function, timeout, *args): + """`function(*args)`, or None once `timeout` passes (the call may finish later, unseen).""" + future = _POOL.submit(function, *args) + try: + return future.result(timeout=max(timeout, 0.001)) + except concurrent.futures.TimeoutError: + return None class Resolver: """Platform first, then the last good platform answer, then DNS-over-HTTPS.""" - def __init__(self, upstreams=None, doh=None, clock=time.monotonic, exchange=None, doh_exchange=None): - self._upstreams = upstreams or (lambda: upstreams_from(read_resolv_conf())) + def __init__(self, upstreams=None, doh=None, clock=time.monotonic, exchange=None, doh_exchange=None, source=None): + if source is None: + source = (lambda: (None, upstreams())) if upstreams else resolv_conf_source + self._source = source self._doh = DOH_ENDPOINTS if doh is None else doh self._clock = clock self._exchange = exchange or exchange_dns @@ -183,52 +244,56 @@ class Resolver: self._cache_for = None self._lock = threading.Lock() - def _cached(self, servers, key, answer=None): + def _cached(self, generation, key, answer=None): with self._lock: - # A clone resumes with the snapshot's cache; another resolver set - # (a TLS rule arrived) means those answers are not the platform's now. - if self._cache_for != servers: + # Every platform write (a TLS rule, a resume, a clone's first boot) + # starts over: a steered name may now answer differently. + if self._cache_for != generation: self._cache.clear() - self._cache_for = servers + self._cache_for = generation + now = self._clock() if answer is not None: - self._cache[key] = answer + self._cache[key] = (now, answer) self._cache.move_to_end(key) while len(self._cache) > CACHE_ENTRIES: self._cache.popitem(last=False) return answer hit = self._cache.get(key) - if hit is not None: - self._cache.move_to_end(key) - return hit + if hit is None or now - hit[0] > STALE_MAX_AGE: + return None + self._cache.move_to_end(key) + return hit[1] def resolve(self, query, tcp=False): key = question_key(query) if key is None: - return failure(query) if len(query) >= 12 else None + return None deadline = self._clock() + QUERY_BUDGET - servers = tuple(self._upstreams()) - # A lone upstream gets a second try: most failures last under a second. - attempts = list(servers) if len(servers) > 1 else list(servers) * 2 + stamp, servers = self._source() + generation = (stamp, tuple(servers)) + attempts = (list(servers) * UPSTREAM_TRIES)[:UPSTREAM_TRIES] for server in attempts: remaining = deadline - self._clock() if remaining <= 0: break - reply = self._exchange(server, query, min(UPSTREAM_TIMEOUT, remaining), tcp) - if usable(reply): + timeout = min(UPSTREAM_TIMEOUT, remaining) + reply = bounded(self._exchange, timeout, server, query, timeout, tcp) + if usable(reply) and answers(reply, query): if reply[2] & 0x02 == 0: - self._cached(servers, key, reply) + self._cached(generation, key, reply) return reply - stale = self._cached(servers, key) + stale = self._cached(generation, key) if stale is not None: return with_id(stale, query) for host, path in self._doh: remaining = deadline - self._clock() if remaining <= 0: break - reply = self._doh_exchange(host, path, query, min(DOH_TIMEOUT, remaining)) + timeout = min(DOH_TIMEOUT, remaining) + reply = bounded(self._doh_exchange, timeout, host, path, query, timeout) # Never NXDOMAIN: public resolvers do not know the names only the # platform answers, and an outage must not look like a missing name. - if found(reply): + if found(reply) and answers(reply, query): return with_id(reply, query) return failure(query) @@ -297,7 +362,7 @@ def serve(resolver, host=LISTEN, port=53): reply = resolver.resolve(query) if reply is None: return - if len(reply) > UDP_ANSWER_LIMIT and len(reply) > 512: + if len(reply) > udp_limit(query): reply = truncated(reply, query) sock.sendto(reply, self.client_address) @@ -327,6 +392,14 @@ def serve(resolver, host=LISTEN, port=53): return udp, tcp +def probe(name="github.com"): + """Exit 0 when the stub itself (not libc's fallback) answers NOERROR for `name`.""" + labels = b"".join(bytes([len(part)]) + part.encode() for part in name.split(".")) + query = struct.pack("!HHHHHH", os.getpid() & 0xFFFF, 0x0100, 1, 0, 0, 0) + labels + b"\x00\x00\x01\x00\x01" + reply = exchange_dns(LISTEN, query, QUERY_BUDGET + 0.5, False) + return 0 if found(reply) and answers(reply, query) else 1 + + def main(argv): command = argv[1] if len(argv) > 1 else "" if command == "guard": @@ -336,7 +409,9 @@ def main(argv): udp, _ = serve(Resolver()) udp.serve_forever() return 0 - print("usage: cmux-dns serve|guard", file=sys.stderr) + if command == "probe": + return probe(*argv[2:3]) + print("usage: cmux-dns serve|guard|probe [name]", file=sys.stderr) return 2 diff --git a/web/services/vms/networkPolicy.ts b/web/services/vms/networkPolicy.ts index 277ebc8a4f23..cab5311c6e33 100644 --- a/web/services/vms/networkPolicy.ts +++ b/web/services/vms/networkPolicy.ts @@ -10,8 +10,9 @@ * - `none`: only the hosts cmux itself needs. * * Ranges become provider firewall rules (IP layer). Domains become provider - * TLS egress rules: the provider steers the exact name to its edge through the - * guest's `/etc/hosts`, so a domain works with no IP-layer grant and no DNS. + * TLS egress rules: the provider's forwarding resolver answers the exact name + * with its edge address, so a domain works with no IP-layer grant and no open + * DNS (allowDns). * Domains are exact names on HTTPS/443 only; wildcards are not steerable yet. * * The policy is data owned by the control plane. The driver reconciles the diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts index 322384e39b4f..18b9018a21f4 100644 --- a/web/tests/vm-devbox-dns.test.ts +++ b/web/tests/vm-devbox-dns.test.ts @@ -85,7 +85,7 @@ print(dns.rcode(nodata.resolve(q("github.com", qtype=28)))) expect(await python(String.raw` r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 2), doh_exchange=lambda h, p, query, t: reply(query, 3)) out = r.resolve(q("coderouter.cmux.internal")) -print(dns.rcode(out), out[3] & 0x80 != 0, dns.question_key(out) == dns.question_key(q("coderouter.cmux.internal"))) +print(dns.rcode(out), out[3] & 0x80 != 0, out[12:] == q("coderouter.cmux.internal")[12:]) dead = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda *a: None) print(dns.rcode(dead.resolve(q("github.com")))) `)).toEqual(["2 True True", "2"]); @@ -149,6 +149,85 @@ udp.shutdown(); tcp.shutdown() }); }); +describe("cmux-dns resolver edge cases", () => { + test("names that differ only in label boundaries never share a cache entry", async () => { + // "github.com" as one label and github + com are different questions. + expect(await python(String.raw` +one_label = struct.pack("!HHHHHH", 1, 0x0100, 1, 0, 0, 0) + b"\x0agithub.com\x00\x00\x01\x00\x01" +print(dns.question_key(one_label) == dns.question_key(q("github.com")), dns.question_key(q("GitHub.COM")) == dns.question_key(q("github.com"))) +state = {"down": False} +def exchange(server, query, timeout, tcp): + return None if state["down"] else reply(query, 3) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda h, p, query, t: reply(query, 0, 1)) +r.resolve(one_label) +state["down"] = True +print(dns.rcode(r.resolve(q("github.com")))) +`)).toEqual(["False True", "0"]); + }); + + test("a stale answer is served at most a day, and never across a platform rewrite", async () => { + // A TLS rule or a resume rewrites resolv.conf with the same resolver + // (10.32.0.28), yet a steered name may now answer differently. + expect(await python(String.raw` +now = [0.0] +stamp = [1] +state = {"down": False} +def exchange(server, query, timeout, tcp): + return None if state["down"] else reply(query, 0, 1) +r = dns.Resolver(source=lambda: (stamp[0], ["10.32.0.28"]), clock=lambda: now[0], exchange=exchange, doh_exchange=lambda *a: None) +r.resolve(q("api.openai.com")) +state["down"] = True +print("same write", dns.rcode(r.resolve(q("api.openai.com")))) +now[0] += dns.STALE_MAX_AGE + 1 +print("a day later", dns.rcode(r.resolve(q("api.openai.com")))) +state["down"] = False; now[0] = 0.0; r.resolve(q("api.openai.com")); state["down"] = True +stamp[0] = 2 +print("after rewrite", dns.rcode(r.resolve(q("api.openai.com")))) +`)).toEqual(["same write 0", "a day later 2", "after rewrite 2"]); + }); + + test("a hung exchange cannot hold a query past its budget", async () => { + expect(await python(String.raw` +import time +def hang(*args): + time.sleep(30) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=hang, doh_exchange=hang) +start = time.monotonic() +out = r.resolve(q("github.com")) +print(dns.rcode(out), time.monotonic() - start < dns.QUERY_BUDGET + 0.5, flush=True) +import os; os._exit(0) # the hung workers would hold interpreter exit for 30 s +`)).toEqual(["2 True"]); + }); + + test("a reply for another question is discarded", async () => { + expect(await python(String.raw` +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(q("evil.example", ident=0x1234), 0, 1), doh_exchange=lambda *a: None) +print(dns.rcode(r.resolve(q("github.com")))) +`)).toEqual(["2"]); + }); + + test("UDP replies respect the client's size: 512 without EDNS, its EDNS size up to 1232", async () => { + expect(await python(String.raw` +plain = q("github.com") +edns = plain[:10] + b"\x00\x01" + plain[12:] + b"\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00" +small = edns[:-11] + b"\x00\x00\x29\x02\x00\x00\x00\x00\x00\x00\x00" +print(dns.udp_limit(plain), dns.udp_limit(edns), dns.udp_limit(small)) +big = reply(plain, 0, 40) +cut = dns.truncated(big, plain) +print(len(big) > 512, dns.rcode(cut), cut[2] & 0x02 != 0, cut[12:] == plain[12:], len(cut) < 512) +`)).toEqual(["512 1232 512", "True 0 True True True"]); + }); + + test("a response or a query without exactly one question gets no answer", async () => { + expect(await python(String.raw` +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: calls.append("platform"), doh_exchange=lambda *a: None) +none = struct.pack("!HHHHHH", 5, 0x0100, 0, 0, 0, 1) + b"\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00" +response = reply(q("github.com"), 0, 1) +print(r.resolve(none), r.resolve(response), calls) +`)).toEqual(["None None []"]); + }); +}); + describe("cmux-dns guard", () => { test("points the platform's resolv.conf at the stub and keeps its resolvers after it", async () => { const directory = fixture(); @@ -163,25 +242,27 @@ print(dns.upstreams_from(open(sys.argv[2]).read())) "# cmux-dns upstream 10.32.0.28", "nameserver 127.0.0.2", "nameserver 10.32.0.28", - "options timeout:4 attempts:2", + "options timeout:4 attempts:1", "", ]); }); - test("records every base resolver though libc reads only three nameservers", async () => { + test("records every base resolver, keeps libc's IPv4 fallback, and keeps other options", async () => { + // libc reads three nameservers; with the stub first, the two left must be + // ones every machine can route. const directory = fixture(); const conf = path.join(directory, "resolv.conf"); - writeFileSync(conf, "search corp.example\nnameserver 2606:4700:4700::1111\nnameserver 2001:4860:4860::8888\nnameserver 8.8.8.8\nnameserver 8.8.4.4\n"); + writeFileSync(conf, "search corp.example\noptions timeout:1 attempts:2 edns0 trust-ad\nnameserver 2606:4700:4700::1111\nnameserver 2001:4860:4860::8888\nnameserver 8.8.8.8\nnameserver 8.8.4.4\n"); expect(await python(String.raw` dns.guard(sys.argv[2]) text = open(sys.argv[2]).read() print(dns.upstreams_from(text)) print(dns.nameservers(text)) -print([line for line in text.splitlines() if line.startswith("search")]) +print([line for line in text.splitlines() if line.startswith(("search", "options"))]) `, conf)).toEqual([ "['2606:4700:4700::1111', '2001:4860:4860::8888', '8.8.8.8', '8.8.4.4']", - "['127.0.0.2', '2606:4700:4700::1111', '2001:4860:4860::8888']", - "['search corp.example']", + "['127.0.0.2', '8.8.8.8', '8.8.4.4']", + "['search corp.example', 'options timeout:4 attempts:1 edns0 trust-ad']", ]); }); From d35893f4918b445a455e6b223336eb3f0b9087e8 Mon Sep 17 00:00:00 2001 From: Austin Wang <38676809+austinywang@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:38:14 -0700 Subject: [PATCH 04/14] chore(cloud): promote devbox images with the DNS fallback (#26640) promote-devbox-image.ts from 2120cc6d1bb7 (branch bake, shared slugs left on the current images): desktop+base defaults at every size, verified, sizes derived and re-booted. sm sh-09b60a58b18b43358087e162f031ff63, md sh-dc5e1b806b944badaf9caa0f675f28f1, lg sh-25f41039030549369279912ac2d680df, lgx sh-e022cc6f289e41199d7ddf0e51a8022c, xl sh-c2f617916547404ba39d1ec717fca02d, 2xl sh-d9ea06066c0f41c0af2af43dd820d7b1. Co-Authored-By: Claude Opus 5.5 --- web/services/vms/images/manifest.json | 455 +++++++++++++++++++++++++- 1 file changed, 443 insertions(+), 12 deletions(-) diff --git a/web/services/vms/images/manifest.json b/web/services/vms/images/manifest.json index a95b2d645448..8ffdf4f4b5a6 100644 --- a/web/services/vms/images/manifest.json +++ b/web/services/vms/images/manifest.json @@ -23300,7 +23300,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "sm", "cpu": 2, @@ -23336,7 +23336,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "md", "cpu": 4, @@ -23372,7 +23372,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lg", "cpu": 8, @@ -23408,7 +23408,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lgx", "cpu": 12, @@ -23443,7 +23443,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "xl", "cpu": 16, @@ -23479,7 +23479,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "2xl", "cpu": 32, @@ -23515,7 +23515,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "sm", "cpu": 2, @@ -23523,7 +23523,7 @@ "storageMb": 16384, "freestyleBase": "freestyle/ubuntu-sm" }, - "defaultForLocalDev": true + "defaultForLocalDev": false }, { "provider": "freestyle", @@ -23552,7 +23552,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "md", "cpu": 4, @@ -23588,7 +23588,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lg", "cpu": 8, @@ -23624,7 +23624,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lgx", "cpu": 12, @@ -23659,7 +23659,7 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "xl", "cpu": 16, @@ -23695,6 +23695,437 @@ "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a9b104f3cb, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-15 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-49bfbb35413c45d9933343ad0aeffdeb, md=sh-cb5ebfea11fd47f098851710b380357d, lgx=sh-db52a32aeae04aedbb233622cd8ab00b, lg=sh-6d0027069c4443feb75956d4510c42c3, xl=sh-5461830dfb924d1eae3e3de0910157cc, 2xl=sh-97347c6bd31c40e0ba554361ebd6a743.", "cmuxTuiCommit": "a9b104f3cb7d3808cf7eb5ae08b4819333b25870", "cmuxTuiSha256": "c310ed196e9db24349d235377b006382f4e30ec69b6e00c7e87b41fc79652500", + "defaultForKind": false, + "size": { + "name": "2xl", + "cpu": 32, + "memoryMb": 65536, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-2xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-sm", + "imageId": "sh-09b60a58b18b43358087e162f031ff63", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "sm", + "cpu": 2, + "memoryMb": 4096, + "storageMb": 16384, + "freestyleBase": "freestyle/ubuntu-sm" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-md", + "imageId": "sh-dc5e1b806b944badaf9caa0f675f28f1", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "md", + "cpu": 4, + "memoryMb": 8192, + "storageMb": 32768, + "freestyleBase": "freestyle/ubuntu" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-lg", + "imageId": "sh-25f41039030549369279912ac2d680df", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "lg", + "cpu": 8, + "memoryMb": 16384, + "storageMb": 65536, + "freestyleBase": "freestyle/ubuntu-lg" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-lgx", + "imageId": "sh-e022cc6f289e41199d7ddf0e51a8022c", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "lgx", + "cpu": 12, + "memoryMb": 24576, + "storageMb": 98304 + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-xl", + "imageId": "sh-c2f617916547404ba39d1ec717fca02d", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "xl", + "cpu": 16, + "memoryMb": 32768, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-2xl", + "imageId": "sh-d9ea06066c0f41c0af2af43dd820d7b1", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "2xl", + "cpu": 32, + "memoryMb": 65536, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-2xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-sm-base", + "imageId": "sh-09b60a58b18b43358087e162f031ff63", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "sm", + "cpu": 2, + "memoryMb": 4096, + "storageMb": 16384, + "freestyleBase": "freestyle/ubuntu-sm" + }, + "defaultForLocalDev": true + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-md-base", + "imageId": "sh-dc5e1b806b944badaf9caa0f675f28f1", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "md", + "cpu": 4, + "memoryMb": 8192, + "storageMb": 32768, + "freestyleBase": "freestyle/ubuntu" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-lg-base", + "imageId": "sh-25f41039030549369279912ac2d680df", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "lg", + "cpu": 8, + "memoryMb": 16384, + "storageMb": 65536, + "freestyleBase": "freestyle/ubuntu-lg" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-lgx-base", + "imageId": "sh-e022cc6f289e41199d7ddf0e51a8022c", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "lgx", + "cpu": 12, + "memoryMb": 24576, + "storageMb": 98304 + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-xl-base", + "imageId": "sh-c2f617916547404ba39d1ec717fca02d", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "defaultForKind": true, + "size": { + "name": "xl", + "cpu": 16, + "memoryMb": 32768, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-26640-dns-2xl-base", + "imageId": "sh-d9ea06066c0f41c0af2af43dd820d7b1", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "epoch": "2026-10-14-r1", + "devboxSource": { + "layers": "desktop", + "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "schema": 2 + }, + "builtAt": "2026-10-28T05:26:05.030Z", + "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", + "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", + "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", "defaultForKind": true, "size": { "name": "2xl", From 1742015d2a8e56d80aaad64c3e2a9cfb16e2e483 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 17:18:38 -0700 Subject: [PATCH 05/14] test(cloud): cover DNS resolver ordering and queued cancellation --- web/tests/vm-devbox-dns.test.ts | 62 ++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts index 18b9018a21f4..1c0048d3df24 100644 --- a/web/tests/vm-devbox-dns.test.ts +++ b/web/tests/vm-devbox-dns.test.ts @@ -38,6 +38,7 @@ def reply(query, rcode, answers=0, tc=False): calls = [] ` + body, script, ...args]); expect(result.stderr).toBe(""); + expect(result.status).toBe(0); return result.stdout.trim().split("\n"); } @@ -98,6 +99,16 @@ print(dns.rcode(r.resolve(q("missing.example"))), calls) `)).toEqual(["3 []"]); }); + test("tries IPv4 platform resolvers before IPv6 when IPv6 is unavailable", async () => { + expect(await python(String.raw` +def exchange(server, query, timeout, tcp): + calls.append(server) + return None if ":" in server else reply(query, 3) +r = dns.Resolver(upstreams=lambda: dns.DEFAULT_UPSTREAMS, exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +print(dns.rcode(r.resolve(q("missing.example"))), calls) +`)).toEqual(["3 ['8.8.8.8']"]); + }); + test("a new resolver set drops answers cached under the old one", async () => { // A clone resumes with the snapshot's cache; when a TLS rule arrives the // platform's answers for steered names change, and a stale one must not @@ -186,18 +197,51 @@ print("after rewrite", dns.rcode(r.resolve(q("api.openai.com")))) `)).toEqual(["same write 0", "a day later 2", "after rewrite 2"]); }); - test("a hung exchange cannot hold a query past its budget", async () => { + test("queued exchanges are cancelled when their caller times out", async () => { expect(await python(String.raw` -import time +import concurrent.futures, threading +started = threading.Event() +release = threading.Event() +def occupy(): + started.set() + release.wait() +with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + dns._POOL = pool + running = pool.submit(occupy) + assert started.wait(10) + try: + print(dns.bounded(lambda: calls.append("abandoned"), 0.001)) + finally: + release.set() + running.result(timeout=10) + pool.submit(lambda: None).result(timeout=10) +print(calls) +`)).toEqual(["None", "[]"]); + }); + + test("a blocked exchange cannot hold a query indefinitely", async () => { + expect(await python(String.raw` +import threading +release = threading.Event() def hang(*args): - time.sleep(30) + release.wait() r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=hang, doh_exchange=hang) -start = time.monotonic() -out = r.resolve(q("github.com")) -print(dns.rcode(out), time.monotonic() - start < dns.QUERY_BUDGET + 0.5, flush=True) -import os; os._exit(0) # the hung workers would hold interpreter exit for 30 s -`)).toEqual(["2 True"]); - }); +result = [] +done = threading.Event() +def resolve(): + try: + result.append(r.resolve(q("github.com"))) + finally: + done.set() +thread = threading.Thread(target=resolve, daemon=True) +thread.start() +completed = done.wait(15) # Failure watchdog; the injected clock test checks the budget. +release.set() +thread.join(timeout=15) +assert completed and not thread.is_alive() +print(dns.rcode(result[0])) +`)).toEqual(["2"]); + }, 35_000); test("a reply for another question is discarded", async () => { expect(await python(String.raw` From d6cb28ab3a5ef54da3e33247c0e6372f6292f9ca Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 17:47:44 -0700 Subject: [PATCH 06/14] test(cloud): require DNS readiness and preserve TLS steering on outages --- web/tests/vm-devbox-dns.test.ts | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts index 1c0048d3df24..9b71a6726137 100644 --- a/web/tests/vm-devbox-dns.test.ts +++ b/web/tests/vm-devbox-dns.test.ts @@ -92,6 +92,37 @@ print(dns.rcode(dead.resolve(q("github.com")))) `)).toEqual(["2 True True", "2"]); }); + test("TLS steering keeps cached edge answers and fails closed on cold misses", async () => { + const marker = path.join(fixture(), "tls-dns-servers"); + writeFileSync(marker, "10.32.0.28\n"); + expect(await python(String.raw` +import os +state = {"down": False} +def exchange(server, query, timeout, tcp): + return None if state["down"] else reply(query, 0, 1) +def doh(host, path, query, timeout): + calls.append("doh") + return reply(query, 0, 1) +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=doh, steering=lambda: dns.platform_steering(sys.argv[2])) +r.resolve(q("api.anthropic.com")) +state["down"] = True +cached = r.resolve(q("api.anthropic.com")) +print(dns.rcode(cached), cached[-4:].hex(), calls) +print(dns.rcode(r.resolve(q("github.com"))), calls) +os.unlink(sys.argv[2]) +print(dns.rcode(r.resolve(q("github.com"))), calls) +`, marker)).toEqual(["0 0a20001c []", "2 []", "0 ['doh']"]); + }); + + test("an empty steering marker permits DoH and an unreadable marker fails closed", async () => { + const directory = fixture(); + const marker = path.join(directory, "tls-dns-servers"); + writeFileSync(marker, ""); + expect(await python(String.raw` +print(dns.platform_steering(sys.argv[2]), dns.platform_steering(sys.argv[3])) +`, marker, directory)).toEqual(["False True"]); + }); + test("the platform's NXDOMAIN is an answer, not a failure", async () => { expect(await python(String.raw` r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 3), doh_exchange=lambda *a: calls.append("doh")) @@ -158,6 +189,43 @@ print("tcp", dns.rcode(answer), answer[2] & 0x02 != 0) udp.shutdown(); tcp.shutdown() `)).toEqual(["udp 0 True", "tcp 0 False"]); }); + + test("service readiness signals that both DNS listeners can accept queries", async () => { + expect(await python(String.raw` +import os, socket, threading +notify = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) +notify.bind(sys.argv[2]); notify.settimeout(10) +os.environ["NOTIFY_SOCKET"] = sys.argv[2] +r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 0, 1)) +real_serve = dns.serve +servers = [] +def serve(resolver): + udp, tcp = real_serve(resolver, host="127.0.0.1", port=0) + servers.extend([udp, tcp]) + return udp, tcp +dns.serve = serve +dns.Resolver = lambda: r +thread = threading.Thread(target=dns.main, args=(["cmux-dns", "serve"],), daemon=True) +thread.start() +try: + print(notify.recv(100).decode()) + udp, tcp = servers + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client: + client.settimeout(10) + client.sendto(q("github.com"), udp.server_address) + print("udp", dns.rcode(client.recv(4096))) + with socket.create_connection(tcp.server_address, timeout=10) as client: + query = q("github.com") + client.sendall(struct.pack("!H", len(query)) + query) + print("tcp", dns.rcode(dns.read_framed(client))) +finally: + for server in servers: + server.shutdown(); server.server_close() + notify.close() +thread.join(timeout=10) +assert not thread.is_alive() +`, path.join(fixture(), "notify"))).toEqual(["READY=1", "udp 0", "tcp 0"]); + }, 35_000); }); describe("cmux-dns resolver edge cases", () => { From 4942f9e429a1cb6f2b84546828661d39e6155b7a Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 18:17:18 -0700 Subject: [PATCH 07/14] fix(cloud): preserve TLS steering and signal DNS service readiness --- web/scripts/devbox-image-common.ts | 6 ++- web/services/vms/images/devbox/README.md | 14 +++++-- web/services/vms/images/devbox/cmux-dns | 48 ++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/web/scripts/devbox-image-common.ts b/web/scripts/devbox-image-common.ts index 275b6cefe134..1d52bbc58cdf 100644 --- a/web/scripts/devbox-image-common.ts +++ b/web/scripts/devbox-image-common.ts @@ -49,6 +49,7 @@ export const DEVBOX_TEMPLATE_FILES = [ "chrome-managed-policy.json", "cmux-bashrc", "cmux-devbox-boot", + "cmux-dns", "cmux-tui-state-mount", "cmux-motd", "cmux-prompt.bash", @@ -460,6 +461,7 @@ export function devboxSourceManifest( cuaDriver: devboxCuaDriverVersion(dockerfile), ghosttyVersion: devboxGhosttyVersion(dockerfile), files, + dns: { units: DEVBOX_DNS_UNITS, install: devboxDnsInstallCommand }, }; if (schema >= 2) { shell.dockerfileInstructions = createHash("sha256").update(normalizedDockerfileInstructions(dockerfile)).digest("hex"); @@ -1583,7 +1585,7 @@ export const DEVBOX_DNS_UNITS: Readonly> = { "Wants=nss-lookup.target", "", "[Service]", - "Type=simple", + "Type=notify", // The distribution interpreter, not whichever python3 is first on PATH. `ExecStart=/usr/bin/python3 ${DEVBOX_DNS_PATH} serve`, "Restart=always", @@ -1629,7 +1631,7 @@ export const devboxDnsInstallCommand = [ "systemctl start cmux-dns-guard.service", // The stub itself must answer: libc would hide a dead one behind the // platform resolvers listed after it. - `{ for i in $(seq 1 20); do /usr/bin/python3 ${DEVBOX_DNS_PATH} probe && break; sleep 0.5; done; /usr/bin/python3 ${DEVBOX_DNS_PATH} probe; }`, + `/usr/bin/python3 ${DEVBOX_DNS_PATH} probe`, ].join(" && "); /** The fallback is installed, answers on its own, and resolv.conf points at it first. */ diff --git a/web/services/vms/images/devbox/README.md b/web/services/vms/images/devbox/README.md index 17df51db3138..32300a374784 100644 --- a/web/services/vms/images/devbox/README.md +++ b/web/services/vms/images/devbox/README.md @@ -341,14 +341,19 @@ rule change and on resume anyway. `CAP_NET_BIND_SERVICE`) listens on `127.0.0.2:53`, UDP and TCP, and per query: -1. asks the platform's resolvers (two tries of 1 s: the first two resolvers, +1. asks the platform's resolvers (two tries of 1 s: the first two, IPv4 first, or a lone one twice), so steered names keep resolving to the edge and NXDOMAIN stays the platform's; 2. on SERVFAIL, REFUSED or silence, answers with the platform's last good answer for that exact question, at most a day old. The cache starts over whenever resolv.conf is written (a TLS rule, a resume, a clone's first boot), so a stale answer never outlives a steering change; -3. otherwise asks DNS-over-HTTPS at `1.1.1.1` then `8.8.8.8`, addressed by IP +3. while the provider's `/run/freestyle/tls-dns-servers` marker is nonempty, + returns SERVFAIL on a cache miss. The provider exports resolver IPs but no + steered-name list to the guest, so a public answer could bypass a required + TLS route or credential injection, even with full public egress. This + conservatively covers all names until the platform answers again; +4. when TLS steering is inactive, asks DNS-over-HTTPS at `1.1.1.1` then `8.8.8.8`, addressed by IP on 443, which the interception does not touch, and accepts only NOERROR: a public NXDOMAIN for a name only the platform knows comes back as SERVFAIL, never as a missing name. A machine whose network policy blocks @@ -367,8 +372,9 @@ first), keeps its `search` line and options other than `timeout`/`attempts` resolver on a `# cmux-dns upstream` line the stub reads. A stopped stub therefore costs one refused packet before libc asks the platform directly. Between a platform rewrite and the guard (under a second) the machine -resolves the old way. The bake installs it after the prompt sync and waits -until the stub itself answers (`cmux-dns probe`; a libc lookup would pass +resolves the old way. The bake installs it after the prompt sync; systemd +waits for `READY=1` after both listeners bind, then checks that the stub itself +answers (`cmux-dns probe`; a libc lookup would pass through the fallback resolvers), every later bake step resolves through it, and `verify-devbox-image.ts` checks the same contract. The container recipe has none: Docker owns its resolv.conf. Machines baked before it keep diff --git a/web/services/vms/images/devbox/cmux-dns b/web/services/vms/images/devbox/cmux-dns index 105057af1d7d..a987e7716008 100644 --- a/web/services/vms/images/devbox/cmux-dns +++ b/web/services/vms/images/devbox/cmux-dns @@ -13,8 +13,10 @@ every TLS rule change and on resume. names the platform steers to its edge keep resolving to the edge. When they fail it answers from the last good platform answer for the question (at most a day old, and only since the platform last wrote resolv.conf), then over -DNS-over-HTTPS to resolvers addressed by IP on 443, which the interception -does not touch. It never turns a platform failure into NXDOMAIN. +DNS-over-HTTPS to resolvers addressed by IP on 443 when TLS steering is +inactive. While steering is active, a cache miss fails closed: the guest +cannot distinguish steered names from public ones. It never turns a platform +failure into NXDOMAIN. `guard` (run by cmux-dns-guard.path whenever resolv.conf changes) points resolv.conf at the stub and keeps the platform's resolvers after it, so a @@ -35,6 +37,7 @@ import time LISTEN = "127.0.0.2" RESOLV_CONF = "/etc/resolv.conf" +TLS_DNS_SERVERS = "/run/freestyle/tls-dns-servers" MARKER = "# cmux-dns" UPSTREAM_PREFIX = "# cmux-dns upstream " # Freestyle's base resolvers (/etc/systemd/resolved.conf.d/60-freestyle-vms.conf). @@ -217,6 +220,22 @@ def resolv_conf_source(path=RESOLV_CONF): return generation, upstreams_from(read_resolv_conf(path)) +def platform_steering(path=TLS_DNS_SERVERS): + """The provider publishes resolver IPs here while TLS steering is active. + + It does not publish the steered names. Public answers could bypass TLS + routing (including credential injection), so an unreadable marker also + fails closed. An absent or empty marker means steering is inactive. + """ + try: + with open(path) as stream: + return bool(stream.read().strip()) + except FileNotFoundError: + return False + except OSError: + return True + + _POOL = concurrent.futures.ThreadPoolExecutor(max_workers=64) @@ -226,16 +245,18 @@ def bounded(function, timeout, *args): try: return future.result(timeout=max(timeout, 0.001)) except concurrent.futures.TimeoutError: + future.cancel() return None class Resolver: """Platform first, then the last good platform answer, then DNS-over-HTTPS.""" - def __init__(self, upstreams=None, doh=None, clock=time.monotonic, exchange=None, doh_exchange=None, source=None): + def __init__(self, upstreams=None, doh=None, clock=time.monotonic, exchange=None, doh_exchange=None, source=None, steering=platform_steering): if source is None: source = (lambda: (None, upstreams())) if upstreams else resolv_conf_source self._source = source + self._steering = steering self._doh = DOH_ENDPOINTS if doh is None else doh self._clock = clock self._exchange = exchange or exchange_dns @@ -271,7 +292,9 @@ class Resolver: deadline = self._clock() + QUERY_BUDGET stamp, servers = self._source() generation = (stamp, tuple(servers)) - attempts = (list(servers) * UPSTREAM_TRIES)[:UPSTREAM_TRIES] + # Match libc's fallback order: not every machine routes IPv6. + ordered = sorted(servers, key=lambda server: ":" in server) + attempts = (ordered * UPSTREAM_TRIES)[:UPSTREAM_TRIES] for server in attempts: remaining = deadline - self._clock() if remaining <= 0: @@ -285,6 +308,10 @@ class Resolver: stale = self._cached(generation, key) if stale is not None: return with_id(stale, query) + # The provider exports resolver IPs, but no guest-readable name set. + # Conservatively preserve every possible TLS route on a cold miss. + if self._steering(): + return failure(query) for host, path in self._doh: remaining = deadline - self._clock() if remaining <= 0: @@ -400,6 +427,18 @@ def probe(name="github.com"): return 0 if found(reply) and answers(reply, query) else 1 +def notify_ready(): + """Tell systemd both DNS listeners are bound before it starts dependents.""" + path = os.environ.get("NOTIFY_SOCKET") + if not path: + return + if path.startswith("@"): + path = "\0" + path[1:] + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as notify: + notify.connect(path) + notify.send(b"READY=1") + + def main(argv): command = argv[1] if len(argv) > 1 else "" if command == "guard": @@ -407,6 +446,7 @@ def main(argv): return 0 if command == "serve": udp, _ = serve(Resolver()) + notify_ready() udp.serve_forever() return 0 if command == "probe": From b14bd068d1e9d89e000c78ec228f55ca863f8ca3 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 18:41:39 -0700 Subject: [PATCH 08/14] test(cloud): distinguish DNS record types and isolate steering fixtures --- web/tests/vm-devbox-dns.test.ts | 52 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts index 9b71a6726137..3f84ac0ef130 100644 --- a/web/tests/vm-devbox-dns.test.ts +++ b/web/tests/vm-devbox-dns.test.ts @@ -48,7 +48,7 @@ describe("cmux-dns resolver", () => { def exchange(server, query, timeout, tcp): calls.append(server) return reply(query, 0, 1) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) out = r.resolve(q("api.anthropic.com")) print(dns.rcode(out), out[-4:].hex(), calls) `)).toEqual(["0 0a20001c ['10.32.0.28']"]); @@ -60,7 +60,7 @@ state = {"down": False} def exchange(server, query, timeout, tcp): calls.append("platform") return reply(query, 2) if state["down"] else reply(query, 0, 1) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) r.resolve(q("api.anthropic.com", ident=1)) state["down"] = True calls.clear() @@ -74,20 +74,20 @@ print(dns.rcode(out), struct.unpack("!H", out[:2])[0], out[-4:].hex(), calls) def doh(host, path, query, timeout): calls.append(host) return reply(query, 0, 1) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=doh) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=doh) out = r.resolve(q("github.com", ident=9)) print(dns.rcode(out), struct.unpack("!H", out[:2])[0], calls) -nodata = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda h, p, query, t: reply(query, 0, 0)) +nodata = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda h, p, query, t: reply(query, 0, 0)) print(dns.rcode(nodata.resolve(q("github.com", qtype=28)))) `)).toEqual(["0 9 ['1.1.1.1']", "0"]); }); test("a fallback NXDOMAIN or a total outage answers SERVFAIL, never a missing name", async () => { expect(await python(String.raw` -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 2), doh_exchange=lambda h, p, query, t: reply(query, 3)) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 2), doh_exchange=lambda h, p, query, t: reply(query, 3)) out = r.resolve(q("coderouter.cmux.internal")) print(dns.rcode(out), out[3] & 0x80 != 0, out[12:] == q("coderouter.cmux.internal")[12:]) -dead = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda *a: None) +dead = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: None, doh_exchange=lambda *a: None) print(dns.rcode(dead.resolve(q("github.com")))) `)).toEqual(["2 True True", "2"]); }); @@ -125,7 +125,7 @@ print(dns.platform_steering(sys.argv[2]), dns.platform_steering(sys.argv[3])) test("the platform's NXDOMAIN is an answer, not a failure", async () => { expect(await python(String.raw` -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 3), doh_exchange=lambda *a: calls.append("doh")) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 3), doh_exchange=lambda *a: calls.append("doh")) print(dns.rcode(r.resolve(q("missing.example"))), calls) `)).toEqual(["3 []"]); }); @@ -135,7 +135,7 @@ print(dns.rcode(r.resolve(q("missing.example"))), calls) def exchange(server, query, timeout, tcp): calls.append(server) return None if ":" in server else reply(query, 3) -r = dns.Resolver(upstreams=lambda: dns.DEFAULT_UPSTREAMS, exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: dns.DEFAULT_UPSTREAMS, exchange=exchange, doh_exchange=lambda *a: calls.append("doh")) print(dns.rcode(r.resolve(q("missing.example"))), calls) `)).toEqual(["3 ['8.8.8.8']"]); }); @@ -149,7 +149,7 @@ servers = ["8.8.8.8"] state = {"down": False} def exchange(server, query, timeout, tcp): return None if state["down"] else reply(query, 0, 1) -r = dns.Resolver(upstreams=lambda: servers, exchange=exchange, doh_exchange=lambda *a: None) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: servers, exchange=exchange, doh_exchange=lambda *a: None) r.resolve(q("api.openai.com")) servers[:] = ["10.32.0.28"] state["down"] = True @@ -166,7 +166,7 @@ def exchange(server, query, timeout, tcp): def doh(host, path, query, timeout): now[0] += timeout calls.append((host, round(timeout, 2))) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], clock=lambda: now[0], exchange=exchange, doh_exchange=doh) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], clock=lambda: now[0], exchange=exchange, doh_exchange=doh) r.resolve(q("github.com")) print(calls, now[0] <= dns.QUERY_BUDGET) `)).toEqual(["[1.0, 1.0, ('1.1.1.1', 1.5)] True"]); @@ -177,7 +177,7 @@ print(calls, now[0] <= dns.QUERY_BUDGET) import socket def exchange(server, query, timeout, tcp): return reply(query, 0, 1, tc=not tcp) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: None) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: None) udp, tcp = dns.serve(r, host="127.0.0.1", port=0) import threading; threading.Thread(target=udp.serve_forever, daemon=True).start() c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); c.settimeout(5) @@ -196,7 +196,7 @@ import os, socket, threading notify = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) notify.bind(sys.argv[2]); notify.settimeout(10) os.environ["NOTIFY_SOCKET"] = sys.argv[2] -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 0, 1)) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(query, 0, 1)) real_serve = dns.serve servers = [] def serve(resolver): @@ -229,6 +229,23 @@ assert not thread.is_alive() }); describe("cmux-dns resolver edge cases", () => { + test("question matching preserves binary record types and classes", async () => { + expect(await python(String.raw` +https = q("GitHub.COM", qtype=65) +other = q("github.com", qtype=97) +other_class = https[:-2] + b"\x00\x21" +print(dns.question_key(https) == dns.question_key(other)) +print(dns.answers(reply(other, 0, 1), https), dns.answers(reply(other_class, 0, 1), https)) +state = {"down": False} +def exchange(server, query, timeout, tcp): + return None if state["down"] else reply(query, 0, 1) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda *a: None) +r.resolve(https) +state["down"] = True +print(dns.rcode(r.resolve(other))) +`)).toEqual(["False", "False False", "2"]); + }); + test("names that differ only in label boundaries never share a cache entry", async () => { // "github.com" as one label and github + com are different questions. expect(await python(String.raw` @@ -237,7 +254,7 @@ print(dns.question_key(one_label) == dns.question_key(q("github.com")), dns.ques state = {"down": False} def exchange(server, query, timeout, tcp): return None if state["down"] else reply(query, 3) -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda h, p, query, t: reply(query, 0, 1)) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=exchange, doh_exchange=lambda h, p, query, t: reply(query, 0, 1)) r.resolve(one_label) state["down"] = True print(dns.rcode(r.resolve(q("github.com")))) @@ -253,7 +270,7 @@ stamp = [1] state = {"down": False} def exchange(server, query, timeout, tcp): return None if state["down"] else reply(query, 0, 1) -r = dns.Resolver(source=lambda: (stamp[0], ["10.32.0.28"]), clock=lambda: now[0], exchange=exchange, doh_exchange=lambda *a: None) +r = dns.Resolver(steering=lambda: False, source=lambda: (stamp[0], ["10.32.0.28"]), clock=lambda: now[0], exchange=exchange, doh_exchange=lambda *a: None) r.resolve(q("api.openai.com")) state["down"] = True print("same write", dns.rcode(r.resolve(q("api.openai.com")))) @@ -293,7 +310,7 @@ import threading release = threading.Event() def hang(*args): release.wait() -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=hang, doh_exchange=hang) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=hang, doh_exchange=hang) result = [] done = threading.Event() def resolve(): @@ -313,7 +330,7 @@ print(dns.rcode(result[0])) test("a reply for another question is discarded", async () => { expect(await python(String.raw` -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(q("evil.example", ident=0x1234), 0, 1), doh_exchange=lambda *a: None) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda s, query, t, tcp: reply(q("evil.example", ident=0x1234), 0, 1), doh_exchange=lambda *a: None) print(dns.rcode(r.resolve(q("github.com")))) `)).toEqual(["2"]); }); @@ -332,7 +349,7 @@ print(len(big) > 512, dns.rcode(cut), cut[2] & 0x02 != 0, cut[12:] == plain[12:] test("a response or a query without exactly one question gets no answer", async () => { expect(await python(String.raw` -r = dns.Resolver(upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: calls.append("platform"), doh_exchange=lambda *a: None) +r = dns.Resolver(steering=lambda: False, upstreams=lambda: ["10.32.0.28"], exchange=lambda *a: calls.append("platform"), doh_exchange=lambda *a: None) none = struct.pack("!HHHHHH", 5, 0x0100, 0, 0, 0, 1) + b"\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00" response = reply(q("github.com"), 0, 1) print(r.resolve(none), r.resolve(response), calls) @@ -397,6 +414,7 @@ describe("cmux-dns image contract", () => { "/etc/systemd/system/cmux-dns.service", ]); expect(DEVBOX_DNS_UNITS["/etc/systemd/system/cmux-dns.service"]).toContain(`ExecStart=/usr/bin/python3 ${DEVBOX_DNS_PATH} serve`); + expect(DEVBOX_DNS_UNITS["/etc/systemd/system/cmux-dns.service"]).toContain("Type=notify"); expect(DEVBOX_DNS_UNITS["/etc/systemd/system/cmux-dns-guard.path"]).toContain("PathChanged=/etc/resolv.conf"); expect(devboxDnsCheckCommand).toContain("127.0.0.2"); }); From 2d85f8cf20c72d807851182d156f2c38bd847221 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 21:46:19 -0700 Subject: [PATCH 09/14] fix(cloud): preserve binary DNS types when matching questions --- web/services/vms/images/devbox/cmux-dns | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/services/vms/images/devbox/cmux-dns b/web/services/vms/images/devbox/cmux-dns index a987e7716008..6b99078f7819 100644 --- a/web/services/vms/images/devbox/cmux-dns +++ b/web/services/vms/images/devbox/cmux-dns @@ -79,7 +79,7 @@ def question_end(message): def question_key(message): """The question as wire bytes (length-prefixed labels, lowercased), type and class.""" end = question_end(message) - return None if end is None else message[12:end].lower() + return None if end is None else message[12:end - 4].lower() + message[end - 4:end] def udp_limit(query): @@ -126,7 +126,8 @@ def found(message): def answers(message, query): """`message` replies to `query`'s question (resolvers echo it; anything else is discarded).""" end = question_end(query) - return message is not None and len(message) >= end and message[12:end].lower() == query[12:end].lower() + return (end is not None and message is not None and len(message) >= end + and message[12:end - 4].lower() + message[end - 4:end] == question_key(query)) def read_resolv_conf(path=RESOLV_CONF): From cbf79d05237a0bde1fc7e1c5dbf6fe80011e465d Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 22:02:34 -0700 Subject: [PATCH 10/14] test(cloud): include DNS in image source inventory --- web/tests/vm-devbox-image.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/tests/vm-devbox-image.test.ts b/web/tests/vm-devbox-image.test.ts index 2304310a530c..a04f937ef003 100644 --- a/web/tests/vm-devbox-image.test.ts +++ b/web/tests/vm-devbox-image.test.ts @@ -145,6 +145,7 @@ describe("devbox image template", () => { "cmux-claude", "cmux-codex", "cmux-devbox-boot", + "cmux-dns", "cmux-motd", "cmux-opencode", "cmux-prompt.bash", From 8e1439ed0e2fa043f95aea05de63ecfc3916312a Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 22:19:37 -0700 Subject: [PATCH 11/14] test(cloud): reject DNS results crossing a resolver rewrite --- web/tests/vm-devbox-dns.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/web/tests/vm-devbox-dns.test.ts b/web/tests/vm-devbox-dns.test.ts index 3f84ac0ef130..2aa8e84dee47 100644 --- a/web/tests/vm-devbox-dns.test.ts +++ b/web/tests/vm-devbox-dns.test.ts @@ -282,6 +282,29 @@ print("after rewrite", dns.rcode(r.resolve(q("api.openai.com")))) `)).toEqual(["same write 0", "a day later 2", "after rewrite 2"]); }); + test("a resolver rewrite during an exchange discards old platform, cache and DoH results", async () => { + expect(await python(String.raw` +for phase in ("cache", "platform", "doh"): + stamp = [1] + changing = [False] + steering = [False] + def exchange(server, query, timeout, tcp): + if changing[0] and phase != "doh": + stamp[0] = 2 + steering[0] = True + return None if phase == "doh" or (phase == "cache" and changing[0]) else reply(query, 0, 1) + def doh(host, path, query, timeout): + stamp[0] = 2 + steering[0] = True + return reply(query, 0, 1) + r = dns.Resolver(source=lambda: (stamp[0], ["8.8.8.8"]), exchange=exchange, doh_exchange=doh, steering=lambda: steering[0]) + if phase == "cache": + r.resolve(q("api.anthropic.com")) + changing[0] = True + print(phase, dns.rcode(r.resolve(q("api.anthropic.com")))) +`)).toEqual(["cache 2", "platform 2", "doh 2"]); + }); + test("queued exchanges are cancelled when their caller times out", async () => { expect(await python(String.raw` import concurrent.futures, threading From 802c4bc490db7a687983e68afd18a27d9bdadf94 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 28 Oct 2026 22:25:57 -0700 Subject: [PATCH 12/14] fix(cloud): fence DNS results against resolver rewrites --- web/services/vms/images/devbox/cmux-dns | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/services/vms/images/devbox/cmux-dns b/web/services/vms/images/devbox/cmux-dns index 6b99078f7819..8998fe9f1d3d 100644 --- a/web/services/vms/images/devbox/cmux-dns +++ b/web/services/vms/images/devbox/cmux-dns @@ -286,6 +286,10 @@ class Resolver: self._cache.move_to_end(key) return hit[1] + def _current(self, generation): + stamp, servers = self._source() + return generation == (stamp, tuple(servers)) + def resolve(self, query, tcp=False): key = question_key(query) if key is None: @@ -303,12 +307,14 @@ class Resolver: timeout = min(UPSTREAM_TIMEOUT, remaining) reply = bounded(self._exchange, timeout, server, query, timeout, tcp) if usable(reply) and answers(reply, query): + if not self._current(generation): + return failure(query) if reply[2] & 0x02 == 0: self._cached(generation, key, reply) return reply stale = self._cached(generation, key) if stale is not None: - return with_id(stale, query) + return with_id(stale, query) if self._current(generation) else failure(query) # The provider exports resolver IPs, but no guest-readable name set. # Conservatively preserve every possible TLS route on a cold miss. if self._steering(): @@ -322,6 +328,9 @@ class Resolver: # Never NXDOMAIN: public resolvers do not know the names only the # platform answers, and an outage must not look like a missing name. if found(reply) and answers(reply, query): + # A TLS rule may have arrived while this public lookup ran. + if self._steering() or not self._current(generation): + return failure(query) return with_id(reply, query) return failure(query) From bcf11e8fae1b239c782bade9581b66cdaf0b999b Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 29 Oct 2026 14:59:23 -0700 Subject: [PATCH 13/14] fix(cloud): promote repaired DNS fallback guest images --- web/services/vms/images/manifest.json | 192 +++++++++++++------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/web/services/vms/images/manifest.json b/web/services/vms/images/manifest.json index 8ffdf4f4b5a6..fc9b8c7fc7ec 100644 --- a/web/services/vms/images/manifest.json +++ b/web/services/vms/images/manifest.json @@ -23706,19 +23706,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-sm", - "imageId": "sh-09b60a58b18b43358087e162f031ff63", + "version": "freestyle-cmux-26640-dns-repair-v5-sm", + "imageId": "sh-ae52f310550b46bd8083c0cfb11b88a0", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23728,9 +23728,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "sm", @@ -23742,19 +23742,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-md", - "imageId": "sh-dc5e1b806b944badaf9caa0f675f28f1", + "version": "freestyle-cmux-26640-dns-repair-v5-md", + "imageId": "sh-27003a2796e442e38f9a3afdd78a6401", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23764,9 +23764,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "md", @@ -23778,19 +23778,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-lg", - "imageId": "sh-25f41039030549369279912ac2d680df", + "version": "freestyle-cmux-26640-dns-repair-v5-lg", + "imageId": "sh-68ac6e0a2d3a40a5a76a053260d82a21", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23800,9 +23800,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "lg", @@ -23814,19 +23814,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-lgx", - "imageId": "sh-e022cc6f289e41199d7ddf0e51a8022c", + "version": "freestyle-cmux-26640-dns-repair-v5-lgx", + "imageId": "sh-5614cedb608b4250be3d2503b50c059c", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23836,9 +23836,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "lgx", @@ -23849,19 +23849,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-xl", - "imageId": "sh-c2f617916547404ba39d1ec717fca02d", + "version": "freestyle-cmux-26640-dns-repair-v5-xl", + "imageId": "sh-7c099b08f03b4e11b7d91527138f3944", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23871,9 +23871,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "xl", @@ -23885,19 +23885,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-2xl", - "imageId": "sh-d9ea06066c0f41c0af2af43dd820d7b1", + "version": "freestyle-cmux-26640-dns-repair-v5-2xl", + "imageId": "sh-70cd367e144741f1ad3b41ba4281ea7b", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23907,9 +23907,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "2xl", @@ -23921,19 +23921,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-sm-base", - "imageId": "sh-09b60a58b18b43358087e162f031ff63", + "version": "freestyle-cmux-26640-dns-repair-v5-sm-base", + "imageId": "sh-ae52f310550b46bd8083c0cfb11b88a0", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23943,9 +23943,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "sm", @@ -23958,19 +23958,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-md-base", - "imageId": "sh-dc5e1b806b944badaf9caa0f675f28f1", + "version": "freestyle-cmux-26640-dns-repair-v5-md-base", + "imageId": "sh-27003a2796e442e38f9a3afdd78a6401", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -23980,9 +23980,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "md", @@ -23994,19 +23994,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-lg-base", - "imageId": "sh-25f41039030549369279912ac2d680df", + "version": "freestyle-cmux-26640-dns-repair-v5-lg-base", + "imageId": "sh-68ac6e0a2d3a40a5a76a053260d82a21", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -24016,9 +24016,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "lg", @@ -24030,19 +24030,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-lgx-base", - "imageId": "sh-e022cc6f289e41199d7ddf0e51a8022c", + "version": "freestyle-cmux-26640-dns-repair-v5-lgx-base", + "imageId": "sh-5614cedb608b4250be3d2503b50c059c", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -24052,9 +24052,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "lgx", @@ -24065,19 +24065,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-xl-base", - "imageId": "sh-c2f617916547404ba39d1ec717fca02d", + "version": "freestyle-cmux-26640-dns-repair-v5-xl-base", + "imageId": "sh-7c099b08f03b4e11b7d91527138f3944", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -24087,9 +24087,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "xl", @@ -24101,19 +24101,19 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-26640-dns-2xl-base", - "imageId": "sh-d9ea06066c0f41c0af2af43dd820d7b1", + "version": "freestyle-cmux-26640-dns-repair-v5-2xl-base", + "imageId": "sh-70cd367e144741f1ad3b41ba4281ea7b", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "2120cc6d1bb7e377d9c25d69cddccdca326c459d", + "repoCommit": "ce47d83051fb49adbe44f5b28405b0a02b054290", "epoch": "2026-10-14-r1", "devboxSource": { "layers": "desktop", - "digest": "9497c15f1d27c8dcf7c2e4e90f616da744b911ba3bcf0b627cd525b20eac96da", + "digest": "76e97f3896783ddc450c7ebe114f063e446819bfa5f80d70bcdece7997059dcf", "schema": 2 }, - "builtAt": "2026-10-28T05:26:05.030Z", + "builtAt": "2026-10-29T07:31:44.489Z", "builderScriptVersion": "5a204252cce26381ed608bf0bd4cb3b3456693742349e5fffcefed696b82be31", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.299", @@ -24123,9 +24123,9 @@ "agent-browser": "0.39.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 316d139fea, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-28 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-09b60a58b18b43358087e162f031ff63, md=sh-dc5e1b806b944badaf9caa0f675f28f1, lg=sh-25f41039030549369279912ac2d680df, lgx=sh-e022cc6f289e41199d7ddf0e51a8022c, xl=sh-c2f617916547404ba39d1ec717fca02d, 2xl=sh-d9ea06066c0f41c0af2af43dd820d7b1.", - "cmuxTuiCommit": "316d139feaf89d9ab7c99dc9cffdce59309bef93", - "cmuxTuiSha256": "5f77f4ac3d03132e4c340572e52f00be6f4f2ac3fcdd52c3497cad3ebee6bc3c", + "notes": "cmux devbox epoch 2026-10-14-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-29 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-ae52f310550b46bd8083c0cfb11b88a0, md=sh-27003a2796e442e38f9a3afdd78a6401, lg=sh-68ac6e0a2d3a40a5a76a053260d82a21, lgx=sh-5614cedb608b4250be3d2503b50c059c, xl=sh-7c099b08f03b4e11b7d91527138f3944, 2xl=sh-70cd367e144741f1ad3b41ba4281ea7b.", + "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", + "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", "defaultForKind": true, "size": { "name": "2xl", From ddadac84468de00cb414234d7302af437d9a2f39 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Mon, 2 Nov 2026 21:05:00 -0800 Subject: [PATCH 14/14] chore(cloud): re-promote devbox images on the merge with main (#26640) The images on main (https://github.com/manaflow-ai/cmux/pull/27152) were baked without cmux-dns, and this branch's images were baked without the bubblewrap change, so neither ladder matched the merged sources. Re-baked, verified and derived from 8bd203bf55 by Cloud VM image contract run 41579771241 (dns-fallback-ok at bake and verify). Co-Authored-By: Claude Opus 5.5 --- web/services/vms/images/manifest.json | 455 +++++++++++++++++++++++++- 1 file changed, 443 insertions(+), 12 deletions(-) diff --git a/web/services/vms/images/manifest.json b/web/services/vms/images/manifest.json index 0d9f925ac32a..a91b5f763751 100644 --- a/web/services/vms/images/manifest.json +++ b/web/services/vms/images/manifest.json @@ -23731,7 +23731,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "sm", "cpu": 2, @@ -23767,7 +23767,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "md", "cpu": 4, @@ -23803,7 +23803,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lg", "cpu": 8, @@ -23839,7 +23839,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lgx", "cpu": 12, @@ -23874,7 +23874,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "xl", "cpu": 16, @@ -23910,7 +23910,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "2xl", "cpu": 32, @@ -23946,7 +23946,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "sm", "cpu": 2, @@ -23954,7 +23954,7 @@ "storageMb": 16384, "freestyleBase": "freestyle/ubuntu-sm" }, - "defaultForLocalDev": true + "defaultForLocalDev": false }, { "provider": "freestyle", @@ -23983,7 +23983,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "md", "cpu": 4, @@ -24019,7 +24019,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lg", "cpu": 8, @@ -24055,7 +24055,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "lgx", "cpu": 12, @@ -24090,7 +24090,7 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", - "defaultForKind": true, + "defaultForKind": false, "size": { "name": "xl", "cpu": 16, @@ -24126,6 +24126,437 @@ "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon a6d89d443f, identity bound to the instance id, no create-time bootstrap. Validated 2026-10-30 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-f6cf988a57f44d64b6099fad5c1f4c1c, md=sh-a7bb4417dc7f463daca86ed17f5e4ef8, lg=sh-d55b743a5a2a4bb1a8ec180f6443013b, lgx=sh-f20273bba9f3491084bd98e39c9f8a69, xl=sh-0f6eda742f494329bff078eb4fbf578e, 2xl=sh-3470ac323a154a9fb14c190d338c2aff.", "cmuxTuiCommit": "a6d89d443f5f9ffb66c296d7a9680e307bbe6c58", "cmuxTuiSha256": "5599b2cd18d3587acfeea2f02d42f8032d725e15b1e23b55620405e61bbe9043", + "defaultForKind": false, + "size": { + "name": "2xl", + "cpu": 32, + "memoryMb": 65536, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-2xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-sm", + "imageId": "sh-8e30265bf89a483193c7ecda059d0d27", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "sm", + "cpu": 2, + "memoryMb": 4096, + "storageMb": 16384, + "freestyleBase": "freestyle/ubuntu-sm" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-md", + "imageId": "sh-9489bb2e7b7742e7931aad1eaa6d4405", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "md", + "cpu": 4, + "memoryMb": 8192, + "storageMb": 32768, + "freestyleBase": "freestyle/ubuntu" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-lg", + "imageId": "sh-4a6af0630f074a37ac329f666ea2eb7c", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "lg", + "cpu": 8, + "memoryMb": 16384, + "storageMb": 65536, + "freestyleBase": "freestyle/ubuntu-lg" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-lgx", + "imageId": "sh-0f76b6ef8a9b45c5b37d79ebfc55bb62", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "lgx", + "cpu": 12, + "memoryMb": 24576, + "storageMb": 98304 + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-xl", + "imageId": "sh-37b38237562a42da982a3dd0c2af05f9", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "xl", + "cpu": 16, + "memoryMb": 32768, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-2xl", + "imageId": "sh-f89d6197d64a4b4b9a50552cbea47674", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "desktop", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "2xl", + "cpu": 32, + "memoryMb": 65536, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-2xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-sm-base", + "imageId": "sh-8e30265bf89a483193c7ecda059d0d27", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "sm", + "cpu": 2, + "memoryMb": 4096, + "storageMb": 16384, + "freestyleBase": "freestyle/ubuntu-sm" + }, + "defaultForLocalDev": true + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-md-base", + "imageId": "sh-9489bb2e7b7742e7931aad1eaa6d4405", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "md", + "cpu": 4, + "memoryMb": 8192, + "storageMb": 32768, + "freestyleBase": "freestyle/ubuntu" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-lg-base", + "imageId": "sh-4a6af0630f074a37ac329f666ea2eb7c", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "lg", + "cpu": 8, + "memoryMb": 16384, + "storageMb": 65536, + "freestyleBase": "freestyle/ubuntu-lg" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-lgx-base", + "imageId": "sh-0f76b6ef8a9b45c5b37d79ebfc55bb62", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "lgx", + "cpu": 12, + "memoryMb": 24576, + "storageMb": 98304 + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-xl-base", + "imageId": "sh-37b38237562a42da982a3dd0c2af05f9", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", + "defaultForKind": true, + "size": { + "name": "xl", + "cpu": 16, + "memoryMb": 32768, + "storageMb": 131072, + "freestyleBase": "freestyle/ubuntu-xl" + } + }, + { + "provider": "freestyle", + "version": "freestyle-cmux-devbox-41579771241-1-2xl-base", + "imageId": "sh-f89d6197d64a4b4b9a50552cbea47674", + "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", + "kind": "base", + "cmuxdRemoteCommit": "none-cmux-tui", + "repoCommit": "8bd203bf55edb3db61eecda6415519bda1bf0c62", + "epoch": "2026-10-28-r1", + "devboxSource": { + "layers": "desktop", + "digest": "e4e1bf79bccbfe0a78d6aa7850f404878ef32485e348e68af1fb530769a89fac", + "schema": 2 + }, + "builtAt": "2026-11-03T04:53:02.774Z", + "builderScriptVersion": "d4c14de2e74f41dc671887ba6d62281c1f598f7615ba30589d54686946bdcf5c", + "agentToolResolvedVersions": { + "@anthropic-ai/claude-code": "2.1.299", + "@openai/codex": "0.162.1", + "opencode-ai": "1.19.0", + "@earendil-works/pi-coding-agent": "1.2.0", + "agent-browser": "0.39.1" + }, + "validationStatus": "passed", + "notes": "cmux devbox epoch 2026-10-28-r1 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 6da40f7e59, identity bound to the instance id, no create-time bootstrap. Validated 2026-11-03 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-8e30265bf89a483193c7ecda059d0d27, md=sh-9489bb2e7b7742e7931aad1eaa6d4405, lg=sh-4a6af0630f074a37ac329f666ea2eb7c, lgx=sh-0f76b6ef8a9b45c5b37d79ebfc55bb62, xl=sh-37b38237562a42da982a3dd0c2af05f9, 2xl=sh-f89d6197d64a4b4b9a50552cbea47674.", + "cmuxTuiCommit": "6da40f7e59b10b8063a91ef58f0d353b64fb405d", + "cmuxTuiSha256": "c9d1a28f4af8d1edaf2969e778e68b89b3a32764e301b3b8628145d99d50e37f", "defaultForKind": true, "size": { "name": "2xl",