Keep Orca runtime metadata owned by the active process (#288)

This commit is contained in:
Jinwoo Hong 2026-04-04 03:01:19 -04:00 committed by GitHub
parent 90d0fae8f5
commit 2f6d96b346
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 13 deletions

View File

@ -5,7 +5,6 @@ import { Store } from './persistence'
import { killAllPty } from './ipc/pty'
import { registerCoreHandlers } from './ipc/register-core-handlers'
import { OrcaRuntimeService } from './runtime/orca-runtime'
import { writeRuntimeMetadata } from './runtime/runtime-metadata'
import { OrcaRuntimeRpcServer } from './runtime/runtime-rpc'
import { registerAppMenu } from './menu/register-app-menu'
import { checkForUpdatesFromMenu, isQuittingForUpdate } from './updater'
@ -60,13 +59,6 @@ app.whenReady().then(async () => {
store = new Store()
runtime = new OrcaRuntimeService(store)
writeRuntimeMetadata(app.getPath('userData'), {
runtimeId: runtime.getRuntimeId(),
pid: process.pid,
transport: null,
authToken: null,
startedAt: runtime.getStartedAt()
})
nativeTheme.themeSource = store.getSettings().theme ?? 'system'
registerAppMenu({

View File

@ -1,12 +1,13 @@
/* eslint-disable max-lines -- Why: this integration-style RPC test keeps the request/response contract together so regressions in the external CLI surface are easier to spot. */
import { mkdtempSync } from 'fs'
import { existsSync, mkdtempSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { createConnection } from 'net'
import { describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import * as runtimeMetadataModule from './runtime-metadata'
import { readRuntimeMetadata } from './runtime-metadata'
import { OrcaRuntimeRpcServer } from './runtime-rpc'
import { createRuntimeTransportMetadata, OrcaRuntimeRpcServer } from './runtime-rpc'
vi.mock('../git/worktree', () => ({
listWorktrees: vi.fn().mockResolvedValue([
@ -107,9 +108,57 @@ describe('OrcaRuntimeRpcServer', () => {
expect(metadata?.runtimeId).toBe(runtime.getRuntimeId())
expect(metadata?.authToken).toBeTruthy()
expect(metadata?.transport?.endpoint).toBeTruthy()
expect(metadata?.transport).toEqual(server['transport'])
await server.stop()
expect(readRuntimeMetadata(userDataPath)).toMatchObject({
runtimeId: runtime.getRuntimeId()
})
})
it('leaves the last published metadata in place when a runtime stops', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
const runtime = new OrcaRuntimeService()
const server = new OrcaRuntimeRpcServer({
runtime,
userDataPath,
pid: 1001
})
await server.start()
const metadata = readRuntimeMetadata(userDataPath)
expect(metadata?.pid).toBe(1001)
await server.stop()
expect(readRuntimeMetadata(userDataPath)).toMatchObject({
pid: 1001,
runtimeId: runtime.getRuntimeId()
})
})
it('closes the socket if metadata publication fails during startup', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
const runtime = new OrcaRuntimeService()
const server = new OrcaRuntimeRpcServer({ runtime, userDataPath })
const writeMetadataSpy = vi
.spyOn(runtimeMetadataModule, 'writeRuntimeMetadata')
.mockImplementationOnce(() => {
throw new Error('write failed')
})
const endpoint = createRuntimeTransportMetadata(
userDataPath,
process.pid,
process.platform,
runtime.getRuntimeId()
).endpoint
await expect(server.start()).rejects.toThrow('write failed')
expect(readRuntimeMetadata(userDataPath)).toBeNull()
expect(existsSync(endpoint)).toBe(false)
expect(server['transport']).toBeNull()
expect(server['server']).toBeNull()
writeMetadataSpy.mockRestore()
})
it('serves status.get for authenticated callers', async () => {

View File

@ -5,7 +5,6 @@ import { chmodSync, existsSync, rmSync } from 'fs'
import { join } from 'path'
import type { OrcaRuntimeService } from './orca-runtime'
import {
clearRuntimeMetadata,
type RuntimeMetadata,
type RuntimeTransportMetadata,
writeRuntimeMetadata
@ -103,9 +102,34 @@ export class OrcaRuntimeRpcServer {
chmodSync(transport.endpoint, 0o600)
}
// Why: publish the transport into in-memory state before writing metadata
// so the bootstrap file always contains the real endpoint/token pair. The
// CLI only discovers the runtime through that file.
this.server = server
this.transport = transport
this.writeMetadata()
try {
this.writeMetadata()
} catch (error) {
// Why: a runtime that cannot publish bootstrap metadata is invisible to
// the `orca` CLI. Close the socket immediately instead of leaving behind
// a live but undiscoverable control plane.
this.server = null
this.transport = null
await new Promise<void>((resolve, reject) => {
server.close((closeError) => {
if (closeError) {
reject(closeError)
return
}
resolve()
})
}).catch(() => {})
if (transport.kind === 'unix' && existsSync(transport.endpoint)) {
rmSync(transport.endpoint, { force: true })
}
throw error
}
}
async stop(): Promise<void> {
@ -113,7 +137,6 @@ export class OrcaRuntimeRpcServer {
const transport = this.transport
this.server = null
this.transport = null
clearRuntimeMetadata(this.userDataPath)
if (!server) {
return
}
@ -129,6 +152,11 @@ export class OrcaRuntimeRpcServer {
if (transport?.kind === 'unix' && existsSync(transport.endpoint)) {
rmSync(transport.endpoint, { force: true })
}
// Why: we intentionally leave the last metadata file behind instead of
// deleting it on shutdown. Shared userData paths can briefly host multiple
// Orca processes during restarts, updates, or development, and stale
// metadata is safer than letting one process erase another live runtime's
// bootstrap file.
}
private handleConnection(socket: Socket): void {