From a91ef524891b8b908dfd4f0052656c86c473fed2 Mon Sep 17 00:00:00 2001 From: Jeremy Liang Date: Sat, 17 Oct 2026 09:10:12 +0800 Subject: [PATCH 1/2] fix: restore baked kernel entry for remote Claude slots --- src/lib/cluster/remote-slot.mjs | 6 ++--- test/unit/cluster-placement.test.mjs | 10 +++++++- test/unit/remote-slot-gpt.test.mjs | 35 ++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/lib/cluster/remote-slot.mjs b/src/lib/cluster/remote-slot.mjs index da6972f7f..85a676fc5 100644 --- a/src/lib/cluster/remote-slot.mjs +++ b/src/lib/cluster/remote-slot.mjs @@ -28,7 +28,7 @@ import { proxyKey, } from '../vm/egress.mjs' import { OS_CATALOG } from '../vm/os-catalog.mjs' -import { resolveKernelDataplane } from '../vm/slot-engine.mjs' +import { REMOTE_KERNEL_ENTRY, resolveKernelDataplane } from '../vm/slot-engine.mjs' import { isCodexVm } from '../vm/vm-kind.mjs' import { proxyBlockedReason } from '../vm/proxy-policy.mjs' import { isKvmRuntime, runtimeKind } from '../vm/runtime-kind.mjs' @@ -42,7 +42,6 @@ import { normalizeTimezone, KVM_CAPS, STANDARD_LOCALE, - slotContainerCommand, writeWorkerFiles, } from '../vm/vm-runtime.mjs' import { @@ -489,7 +488,8 @@ export function slotContainerBody(vm, { image, network, remoteDir, user, routing `KIN_VM_NAME=${slotName}`, `KIN_VM_OS=${vm.kernel}`, ], - Cmd: slotContainerCommand(vm), + // Node images bake the Claude kernel here; local slots bind-mount a different path. + Cmd: codex ? ['sleep', 'infinity'] : [REMOTE_KERNEL_ENTRY, '--gateway-worker', '--config', '/run/kin/kernel.json'], Labels: { 'kin.vm': '1', 'kin.vm.id': vm.id, diff --git a/test/unit/cluster-placement.test.mjs b/test/unit/cluster-placement.test.mjs index f364ea8b0..1d66ba628 100644 --- a/test/unit/cluster-placement.test.mjs +++ b/test/unit/cluster-placement.test.mjs @@ -16,7 +16,7 @@ import { startSlotImageBuild, unbindPlacement, } from '../../src/lib/cluster/placement.mjs' -import { remoteIptablesScript, slotEgressNames } from '../../src/lib/cluster/remote-slot.mjs' +import { remoteIptablesScript, slotContainerBody, slotEgressNames } from '../../src/lib/cluster/remote-slot.mjs' import { pullSlotCredentials, pushSlotCredentials, @@ -283,6 +283,14 @@ test('slot image tag follows payload bytes only; a VERSION bump keeps the image' fs.chmodSync(process.env.KIN_CODEX_KERNEL_BIN, 0o755) const a = slotImageSpec(root, 'ubuntu-24.04') + const container = slotContainerBody( + { id: 'vm-03', platform: 'anthropic', node_id: 'node-a', kernel: 'ubuntu-24.04' }, + { image: a.ref, network: 'test-net', remoteDir: '/home/node/vms/vm-03', user: '1000:1000', routing: {} }, + ) + const entry = a.files.find((file) => `/${file.name}` === container.Cmd[0]) + assert.ok(entry, 'the remote Claude command must name a file baked into its slot image') + assert.equal(entry.mode, 0o755) + assert.match(entry.body.toString(), /^#!\/bin\/sh/) assert.match(a.ref, /^vm2api\/kin-slot-ubuntu-24\.04:[0-9a-f]{12}$/) const kvm = slotImageSpec(root, 'ubuntu-24.04', { runtime: 'kvm' }) assert.match(kvm.ref, /^vm2api\/kin-slot-kvm-ubuntu-24\.04:[0-9a-f]{12}$/) diff --git a/test/unit/remote-slot-gpt.test.mjs b/test/unit/remote-slot-gpt.test.mjs index 8be25a253..08912e9ed 100644 --- a/test/unit/remote-slot-gpt.test.mjs +++ b/test/unit/remote-slot-gpt.test.mjs @@ -2,7 +2,8 @@ import test from 'node:test' import assert from 'node:assert/strict' import { SLOT_CAPS_NODE } from '../../src/lib/cluster/placement.mjs' import { codexRoleMatches, slotContainerBody } from '../../src/lib/cluster/remote-slot.mjs' -import { CODEX_SLOT_ROLE } from '../../src/lib/vm/vm-runtime.mjs' +import { REMOTE_KERNEL_ENTRY } from '../../src/lib/vm/slot-engine.mjs' +import { CODEX_SLOT_ROLE, slotContainerCommand } from '../../src/lib/vm/vm-runtime.mjs' import { slotHost } from '../../src/lib/vm/slot-host.mjs' import { startSlotReady } from '../../src/lib/vm/slot-runtime.mjs' @@ -30,14 +31,40 @@ test('a remote GPT container idles instead of booting the Claude kernel', () => const spec = body(gpt) assert.deepEqual(spec.Cmd, ['sleep', 'infinity']) assert.equal(spec.Labels['kin.vm.role'], CODEX_SLOT_ROLE) - const claudeSpec = body(claude) - assert.deepEqual(claudeSpec.Cmd, [ +}) + +for (const runtime of [undefined, { type: 'docker' }]) { + test(`a remote Claude slot uses the baked kernel entry (${runtime?.type || 'default'} runtime)`, () => { + const spec = body({ ...claude, runtime }) + assert.equal(REMOTE_KERNEL_ENTRY, '/opt/kin/kin-kernel') + assert.deepEqual(spec.Cmd, [REMOTE_KERNEL_ENTRY, '--gateway-worker', '--config', '/run/kin/kernel.json']) + assert.equal(spec.Labels['kin.vm.role'], undefined) + assert.equal(spec.Labels['kin.vm.runtime'], 'docker') + }) +} + +test('local Claude commands retain their bind-mounted and wrapped kernel entries', () => { + assert.deepEqual(slotContainerCommand(claude), [ '/usr/local/bin/kin-kernel', '--gateway-worker', '--config', '/run/kin/kernel.json', ]) - assert.equal(claudeSpec.Labels['kin.vm.role'], undefined) + assert.deepEqual(slotContainerCommand(claude, { wrapKernel: true }), [ + '/home/kincli/.kin/kin-kernel', + '--gateway-worker', + '--config', + '/run/kin/kernel.json', + ]) + assert.deepEqual(slotContainerCommand(gpt), ['sleep', 'infinity']) +}) + +test('remote KVM slots retain the runner image command for both platforms', () => { + for (const vm of [claude, gpt]) { + const spec = body({ ...vm, runtime: { type: 'kvm' } }) + assert.equal(spec.Labels['kin.vm.runtime'], 'kvm') + assert.equal(Object.hasOwn(spec, 'Cmd'), false) + } }) test('a remote GPT container still running kin-kernel is recreated', () => { From c6425a214c31b95c0daef479ad096b948be6c721 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 17 Oct 2026 01:22:44 +0000 Subject: [PATCH 2/2] docs: add unreleased changelog for remote Claude kernel entry (#489, #496) Co-authored-by: ted --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20254c9d8..8625f4e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 未发布 +- 远程 Claude Docker 槽改用镜像烘焙 kernel 入口(`/opt/kin/kin-kernel`,`REMOTE_KERNEL_ENTRY`),不改 GPT 与本机/KVM 命令;新建或重建容器后生效。(#489、#496) - Docker 槽创建可选 `machine.docker_cpus`(`0` 或省略不限;`0.01`–`16` 为 CPU 时间上限):写入槽位记录并在容器创建/重建时生效(本地 `--cpus`、集群 `NanoCpus`),KVM 与非法值拒绝;不提供在线 PATCH 或把 `vcpus` 隐式映射为 Docker 上限;损坏落盘值在 reload/恢复/重建前拒绝。(#490) - 修复通知监控在冷却/无事件轮询时误清 `last_error`:仅在实际投递成功时清除,保留上次投递失败;解析 `dispatchNotify` 的 `ok: false` 与部分通道失败,同批次任一失败优先于成功事件。(#483) - 代理出口地理查询经 SOCKS5 时由代理端解析目标域名(查询路径内将 `socks5://` 规范为 `socks5h:`),避免依赖控制面本地 DNS;IPv4 与 IPv6 两阶段探测一致,不修改已保存代理 URI,HTTP(S) 代理与本地直连行为不变。(#482)