From b65bf8c7603951f5118bbda3db8abf25f1132a22 Mon Sep 17 00:00:00 2001 From: master Date: Thu, 1 Oct 2026 09:10:19 +0000 Subject: [PATCH] fix(orcad): publish the headless graph so terminals can be created orcad never publishes its window layout. The runtime treats that layout as the thing it places work into, so until something publishes one the runtime reports itself unavailable and refuses everything that depends on it. Creating a terminal is refused before it reaches any terminal code, and listing tabs waits on a publication that can never arrive. The daemon is healthy throughout, it is simply never asked. This publishes the same empty placeholder the Electron serve path publishes, after terminal recovery so the layout is adopted against a settled runtime, and before the transport binds so no client can observe the unavailable state. Fixes #17846 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PHbhxM6H9evtLR95vUGs4G --- src/main/orcad/orcad-entry.ts | 5 +++++ .../headless-pty-hydration-ordering.test.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/orcad/orcad-entry.ts b/src/main/orcad/orcad-entry.ts index 6c0e9bbad227..9b15ed4657a0 100644 --- a/src/main/orcad/orcad-entry.ts +++ b/src/main/orcad/orcad-entry.ts @@ -12,6 +12,7 @@ */ import process from 'node:process' import { setAppEnvironment, type AppEnvironment } from '../../shared/app-environment' +import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-session-contracts' import { setSecretStore, type SecretStore } from '../../shared/secret-store' import type { ServeReadiness } from '../server/serve-readiness' import { resolveOrcadInstallRoot, resolveOrcadPath, resolveUserDataPath } from './orcad-app-paths' @@ -291,6 +292,10 @@ async function startOrcadRuntime( // Recovery binds terminal and dispatch identities; only now can startup observations be fenced. observedStatusCapture.attach(runtime) + // Why: nothing else publishes a graph on this host, so `graphStatus` would stay 'unavailable' + // and every graph-gated RPC — terminal create, tab inventory — would fail or hang forever. + runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] }) + const bindHost = resolveOrcadBindHost(options.bind) rpc = new OrcaRuntimeRpcServer({ runtime, diff --git a/src/main/startup/headless-pty-hydration-ordering.test.ts b/src/main/startup/headless-pty-hydration-ordering.test.ts index c761ccf0409f..25e609a444d7 100644 --- a/src/main/startup/headless-pty-hydration-ordering.test.ts +++ b/src/main/startup/headless-pty-hydration-ordering.test.ts @@ -53,6 +53,20 @@ describe('headless PTY registry hydration ordering', () => { expect(readiness).toBeGreaterThan(rpc) }) + it('publishes the orcad headless graph after PTY registration and before RPC', () => { + const source = readFileSync(join(process.cwd(), 'src/main/orcad/orcad-entry.ts'), 'utf8') + const handlersAndHydration = source.indexOf('await registerHeadlessPtyRuntime(') + const sentinel = source.indexOf( + 'runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] })', + handlersAndHydration + ) + const rpc = source.indexOf('rpc = new OrcaRuntimeRpcServer({', sentinel) + + expect(handlersAndHydration).toBeGreaterThanOrEqual(0) + expect(sentinel).toBeGreaterThan(handlersAndHydration) + expect(rpc).toBeGreaterThan(sentinel) + }) + it('starts the orcad hook owner after Store hydration and before daemon PTY recovery', () => { const source = readFileSync(join(process.cwd(), 'src/main/orcad/orcad-entry.ts'), 'utf8') const cleanup = source.indexOf('registerCleanup(async () => {')