From 39113ceac091625f50146fbf49321ff6d4f7d52e Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:20:37 -0400 Subject: [PATCH 1/3] fix(host): refuse a second host on the same home, and close terminals for CLI archive, kill and delete --- apps/host/src/cli.ts | 21 +++++++++++++++++---- apps/host/src/gateway.ts | 21 ++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/host/src/cli.ts b/apps/host/src/cli.ts index 1043ef23..4783397f 100755 --- a/apps/host/src/cli.ts +++ b/apps/host/src/cli.ts @@ -604,6 +604,14 @@ async function main() { case "fork": return console.log((await fork(record.id, catalog.user)).name); case "kill": + // A running host owns the channel's terminals and closes them with it, as for the app. + if (await isHostRunning()) { + return void (await local({ + op: "channel", + channel: record.id, + request: { op: "kill", author: catalog.user }, + })); + } return kill(record.id); case "move": { // `--hosted ` only finishes a move to a hosting service that an earlier version started. @@ -639,11 +647,16 @@ async function main() { ); } case "archive": - return void (await archive(record.id, true)); - case "unarchive": - return void (await archive(record.id, false)); + case "unarchive": { + const archived = command === "archive"; + if (await isHostRunning()) { + return void (await local({ op: "archive", channel: record.id, archived })); + } + return void (await archive(record.id, archived)); + } case "delete": - await remove(record.id); + if (await isHostRunning()) await local({ op: "delete", channel: record.id }); + else await remove(record.id); return console.log(`Deleted ${record.name}. Its lane branches remain in ${record.project}.`); } } diff --git a/apps/host/src/gateway.ts b/apps/host/src/gateway.ts index 312b17e9..908cf5aa 100644 --- a/apps/host/src/gateway.ts +++ b/apps/host/src/gateway.ts @@ -19,7 +19,7 @@ import * as served from "./directory-server"; import { retryLoginShell, shell } from "./environment"; import type { GatewayClient, Watch } from "./gateway-client"; import * as github from "./github"; -import { health, HostConflict } from "./health"; +import { describe, health, HostConflict } from "./health"; import { seal } from "./keys"; import * as lobby from "./lobby"; import { failure, log, open as openLog } from "./log"; @@ -1165,6 +1165,23 @@ async function listen(port: number): Promise> { } } +/** The port of the host serving this home, so a second host on another port finds it. */ +const SERVED = () => join(config.home, "host.port"); + +/** Two hosts on one home would run the same channels' workers and stores twice; the first keeps it. */ +async function claim(port: number) { + const held = Number(await Bun.file(SERVED()).text().catch(() => "")); + if (!held || held === port) return; + const info = await health(held).catch((error) => + error instanceof HostConflict ? error : undefined + ); + if (info?.home !== config.home) return; + const listener = info instanceof HostConflict ? info.listener : describe(info); + throw new Error( + `${config.home} is already served by ${listener.text} on port ${held}. Stop it, or set ACE_HOME to give this host its own data directory.`, + ); +} + /** * `loopback` keeps a disposable host off the team: no tailnet listener, peer discovery, directory * entry, or lobby presence. A second host on this machine would otherwise publish under the @@ -1178,8 +1195,10 @@ export async function serve(port: number, loopback = false, script?: string): Pr // Hosted channels run tools in this process. seal(); openLog("host"); + await claim(port); await resume(); const server = await listen(port); + await Bun.write(SERVED(), String(server.port)); log("info", "host.start", { port: server.port, user: catalog.user, bun: Bun.version }); if (shell.state === "pending") { log("warn", "host.shell", { error: shell.error }); From da83ca8bee783e4f4297b7119567d6f42fc9b55c Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:20:51 -0400 Subject: [PATCH 2/3] chore: confirm host notes and note the home claim --- ...YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md | 6 +++--- ...J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md | 6 +++--- ...YT9Z8YJ2ZWZCVB752WDCFF-workers-and-agent-shells-fin.md | 6 +++--- ...ZH1NMBK54TCB970R3AJ1X1-a-loopback-host-must-not-cal.md | 6 +++--- ...D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md | 6 +++--- ...HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md | 6 +++--- ...J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md | 8 ++++++++ 7 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 .lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md diff --git a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md index 7d935659..0e5f6ecb 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md +++ b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-15T04:47:23Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: e48607de6d665818 -raw_hash: a6282bc5158525e4 -vouched: 2026-10-22T15:17:06Z +body_hash: 2189bea8448ae31b +raw_hash: 1b839377f7aa43ec +vouched: 2026-10-22T15:20:51Z lines: 253-482 --- diff --git a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md index 38fecff7..d5898a48 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md +++ b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-22T14:31:16Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: e48607de6d665818 -raw_hash: a6282bc5158525e4 -vouched: 2026-10-22T15:17:07Z +body_hash: 2189bea8448ae31b +raw_hash: 1b839377f7aa43ec +vouched: 2026-10-22T15:20:51Z lines: 332-614 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M4YT9Z8YJ2ZWZCVB752WDCFF-workers-and-agent-shells-fin.md b/.lane/memory/apps/host/src/gateway.ts/01M4YT9Z8YJ2ZWZCVB752WDCFF-workers-and-agent-shells-fin.md index e6435e12..019ae393 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M4YT9Z8YJ2ZWZCVB752WDCFF-workers-and-agent-shells-fin.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M4YT9Z8YJ2ZWZCVB752WDCFF-workers-and-agent-shells-fin.md @@ -4,9 +4,9 @@ anchor: function serve created: 2026-10-15T03:39:45Z norm: '1' sig: 171797818ef4d836 -body_hash: 1332a8dc2a8a58e7 -raw_hash: 99138b0aa8226285 -vouched: 2026-10-22T15:02:43Z +body_hash: 6333f6a695d3ff98 +raw_hash: ad0574ab5dfd2999 +vouched: 2026-10-22T15:20:51Z lines: 668-751 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M4ZH1NMBK54TCB970R3AJ1X1-a-loopback-host-must-not-cal.md b/.lane/memory/apps/host/src/gateway.ts/01M4ZH1NMBK54TCB970R3AJ1X1-a-loopback-host-must-not-cal.md index 84f37fe3..2a8be3e8 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M4ZH1NMBK54TCB970R3AJ1X1-a-loopback-host-must-not-cal.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M4ZH1NMBK54TCB970R3AJ1X1-a-loopback-host-must-not-cal.md @@ -4,9 +4,9 @@ anchor: function serve created: 2026-10-15T10:17:10Z norm: '1' sig: 171797818ef4d836 -body_hash: 1332a8dc2a8a58e7 -raw_hash: 99138b0aa8226285 -vouched: 2026-10-22T15:02:43Z +body_hash: 6333f6a695d3ff98 +raw_hash: ad0574ab5dfd2999 +vouched: 2026-10-22T15:20:51Z lines: 830-948 supersedes: 01M4Z2R1HEVDKEA8VEJ7XRJQ8F --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md b/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md index e9d41f77..018b3888 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T17:04:56Z norm: '1' sig: 1acdc0f6a0895ac5 -body_hash: 38cce3f1f25177dd -raw_hash: e92b21447ef6a159 -vouched: 2026-10-22T15:09:48Z +body_hash: 2c286a27ab7e9d25 +raw_hash: 0da8b2103ee9b539 +vouched: 2026-10-22T15:20:51Z lines: 1-1153 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md b/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md index 55d94b56..dd4f5c26 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-22T14:00:47Z norm: '1' sig: 1acdc0f6a0895ac5 -body_hash: 38cce3f1f25177dd -raw_hash: e92b21447ef6a159 -vouched: 2026-10-22T15:09:55Z +body_hash: 2c286a27ab7e9d25 +raw_hash: 0da8b2103ee9b539 +vouched: 2026-10-22T15:20:51Z lines: 1-1309 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md b/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md new file mode 100644 index 00000000..258d0373 --- /dev/null +++ b/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md @@ -0,0 +1,8 @@ +--- +id: 01M5J36RXR2GN6T4P61RJKG55Y +anchor: function claim +created: 2026-10-22T15:20:51Z +norm: '1' +--- + +serve checks $ACE_HOME/host.port before resume() and writes it after listen; a second host on another port must refuse while the first still answers /health with the same home From f03024e8bf28768d544c52feb8cc2382e5fba1bd Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:20:53 -0400 Subject: [PATCH 3/3] lane: sync fix-host-safety memory --- ...01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md b/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md index 258d0373..b6882d78 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5J36RXR2GN6T4P61RJKG55Y-serve-checks-ace-home-host-p.md @@ -3,6 +3,10 @@ id: 01M5J36RXR2GN6T4P61RJKG55Y anchor: function claim created: 2026-10-22T15:20:51Z norm: '1' +sig: fe5a25627e23d6bd +body_hash: dffc6420f4a82617 +raw_hash: 5e5be372f64ebf07 +lines: 1172-1183 --- serve checks $ACE_HOME/host.port before resume() and writes it after listen; a second host on another port must refuse while the first still answers /health with the same home