diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift index 7a78df6a4db8..0f0238647aaf 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeRuntime.swift @@ -305,7 +305,15 @@ actor MacNodeRuntime { } private func handleCodexThreadInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse { - guard self.codexThreadCatalogEnabled() else { + // Freeze native ownership before awaiting worker support so one invocation cannot switch owners. + let nativeCatalogEnabled = self.codexThreadCatalogEnabled() + if !nativeCatalogEnabled, + let nodeHostWorker, + await nodeHostWorker.supports(req.command) + { + return await nodeHostWorker.invoke(req) + } + guard nativeCatalogEnabled else { return Self.errorResponse( req, code: .unavailable, diff --git a/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift b/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift index 537394028937..58c0f37d2382 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift @@ -2,6 +2,7 @@ import Darwin import Foundation import OpenClawKit import OpenClawProtocol +import os import Testing @testable import OpenClaw @@ -211,12 +212,33 @@ struct MacNodeHostWorkerTests { } @Test(arguments: [ - ( - MacNodeCodexThreadCatalogContract.listCommand, - "UNAVAILABLE: Codex session catalog is disabled"), - ( - MacNodeCodexThreadCatalogContract.turnsCommand, - "UNAVAILABLE: Codex session catalog is disabled"), + MacNodeCodexThreadCatalogContract.listCommand, + MacNodeCodexThreadCatalogContract.turnsCommand, + ]) + func `worker owns Codex catalog commands when native catalog is disabled`(command: String) async { + let worker = StubMacNodeHostWorker(commands: [command]) + let nativeCatalogEnabledReads = OSAllocatedUnfairLock(initialState: 0) + let runtime = MacNodeRuntime( + nodeHostWorker: worker, + codexThreadCatalogEnabled: { + nativeCatalogEnabledReads.withLock { + $0 += 1 + return false + } + }) + + let response = await runtime.handleInvoke(BridgeInvokeRequest( + id: "worker-codex-catalog", + command: command, + paramsJSON: #"{"limit":1}"#)) + + #expect(response.ok) + #expect(response.payloadJSON == #"{"owner":"cli"}"#) + #expect(await worker.invokedCommands() == [command]) + #expect(nativeCatalogEnabledReads.withLock { $0 } == 1) + } + + @Test(arguments: [ ( MacNodeClaudeSessionCatalogContract.listCommand, "UNAVAILABLE: Claude session catalog is disabled"),