fix(mac): allow slow node worker startup (#106281)

* fix(mac): allow slow node worker startup

* chore: leave release notes to release flow
This commit is contained in:
Peter Steinberger
2026-07-13 05:00:48 -07:00
committed by GitHub
parent bbd6f79b3b
commit bfdfd35e5d
2 changed files with 23 additions and 1 deletions

View File

@@ -27,6 +27,8 @@ protocol MacNodeHostWorking: Sendable {
/// The worker never connects to Gateway; this app remains the sole node identity
/// and keeps TCC-sensitive execution behind the native exec-host socket.
final class MacNodeHostWorker: MacNodeHostWorking, @unchecked Sendable {
nonisolated static let defaultStartupTimeout: TimeInterval = 120
enum WorkerError: LocalizedError {
case unavailable(String)
@@ -41,6 +43,7 @@ final class MacNodeHostWorker: MacNodeHostWorking, @unchecked Sendable {
private let queue = DispatchQueue(label: "ai.openclaw.node-host-worker")
private let writerQueue = DispatchQueue(label: "ai.openclaw.node-host-worker.writer")
private let session: GatewayNodeSession
private let startupTimeout: TimeInterval
private let onUnexpectedExit: @Sendable () -> Void
private var process: Process?
private var stdinPipe: Pipe?
@@ -65,9 +68,11 @@ final class MacNodeHostWorker: MacNodeHostWorking, @unchecked Sendable {
init(
session: GatewayNodeSession,
startupTimeout: TimeInterval = MacNodeHostWorker.defaultStartupTimeout,
onUnexpectedExit: @escaping @Sendable () -> Void = {})
{
self.session = session
self.startupTimeout = startupTimeout
self.onUnexpectedExit = onUnexpectedExit
}
@@ -228,7 +233,9 @@ final class MacNodeHostWorker: MacNodeHostWorking, @unchecked Sendable {
}
let timer = DispatchSource.makeTimerSource(queue: self.queue)
timer.schedule(deadline: .now() + 20)
// Cold config and plugin discovery can exceed the old 20-second bound.
// Keep a hard deadline, but leave enough room for a cold CLI worker start.
timer.schedule(deadline: .now() + self.startupTimeout)
timer.setEventHandler { [weak self] in
guard let self else { return }
let state = self.process?.isRunning == true ? "running" : "exited"

View File

@@ -29,6 +29,21 @@ private actor StubMacNodeHostWorker: MacNodeHostWorking {
@Suite(.serialized)
struct MacNodeHostWorkerTests {
@Test func `worker allows a generous cold-start window`() async throws {
#expect(MacNodeHostWorker.defaultStartupTimeout == 120)
let worker = MacNodeHostWorker(session: GatewayNodeSession(), startupTimeout: 1)
let script = """
sleep 0.1
printf '%s\\n' '{"type":"ready","version":"test","manifest":{"caps":[],"commands":[],"pathEnv":"/usr/bin:/bin"}}'
while IFS= read -r line; do :; done
"""
let manifest = try await worker.start(command: ["/bin/sh", "-c", script])
#expect(manifest.version == "test")
await worker.stop()
}
@Test func `Mac runtime forwards CLI node commands to the shared worker`() async {
let worker = StubMacNodeHostWorker()
let runtime = MacNodeRuntime(nodeHostWorker: worker)