Files
orca/src/main/daemon/daemon-main.ts
T
Brennan Benson a2d438db17 fix(daemon): detect severed macOS TCC attribution behind terminal automation denials (STA-3491) (#12848)
* fix(daemon): detect severed macOS TCC attribution and surface daemon-restart remedy (STA-3491)

macOS pins the detached PTY daemon's TCC responsible process to the app
binary that forked it. Once that binary is deleted (packaged updates
replace the bundle), Accessibility/Automation grants on Orca silently
stop covering every daemon-hosted terminal: osascript/System Events
fails with -25211 no matter what the user grants.

- record spawnerExecPath in the daemon pid file at fork
- adoption checks it: severed + 0 live sessions -> replace the daemon
  (reason severed_tcc_attribution); live sessions are preserved
- Settings (Developer Permissions + Manage Sessions) show a visible
  banner pointing at Manage Sessions -> Restart while severed

* fix(daemon): harden TCC attribution recovery
2026-08-05 23:54:30 -07:00

64 lines
2.5 KiB
TypeScript

import { DaemonServer, type DaemonServerOptions } from './daemon-server'
import type { DaemonFileLog } from './daemon-file-log'
export type DaemonStartOptions = {
socketPath: string
tokenPath: string
pidPath?: string
launchNonce?: string
startedAtMs?: number
publishEndpointOwnership?: DaemonServerOptions['publishEndpointOwnership']
entryPath?: string
appVersion?: string
spawnerExecPath?: string
/** Direct-construction seam for versioned protocol fixtures; never CLI/env configured. */
protocolVersion?: number
spawnSubprocess: DaemonServerOptions['spawnSubprocess']
preparePtySpawn?: DaemonServerOptions['preparePtySpawn']
onPtySessionExit?: DaemonServerOptions['onPtySessionExit']
onAuthenticatedClientPair?: DaemonServerOptions['onAuthenticatedClientPair']
log?: DaemonFileLog
onIdleShutdown?: () => void
onRpcShutdown?: () => void
initialAdoptionTestConfig?: DaemonServerOptions['initialAdoptionTestConfig']
}
export type DaemonHandle = {
shutdown(): Promise<void>
}
export async function startDaemon(opts: DaemonStartOptions): Promise<DaemonHandle> {
const server = new DaemonServer({
socketPath: opts.socketPath,
tokenPath: opts.tokenPath,
...(opts.pidPath ? { pidPath: opts.pidPath } : {}),
...(opts.launchNonce ? { launchNonce: opts.launchNonce } : {}),
...(opts.startedAtMs ? { startedAtMs: opts.startedAtMs } : {}),
...(opts.publishEndpointOwnership
? { publishEndpointOwnership: opts.publishEndpointOwnership }
: {}),
...(opts.entryPath ? { entryPath: opts.entryPath } : {}),
...(opts.appVersion ? { appVersion: opts.appVersion } : {}),
...(opts.spawnerExecPath ? { spawnerExecPath: opts.spawnerExecPath } : {}),
...(opts.protocolVersion !== undefined ? { protocolVersion: opts.protocolVersion } : {}),
spawnSubprocess: opts.spawnSubprocess,
...(opts.preparePtySpawn ? { preparePtySpawn: opts.preparePtySpawn } : {}),
...(opts.onPtySessionExit ? { onPtySessionExit: opts.onPtySessionExit } : {}),
...(opts.onAuthenticatedClientPair
? { onAuthenticatedClientPair: opts.onAuthenticatedClientPair }
: {}),
...(opts.log ? { log: opts.log } : {}),
...(opts.onIdleShutdown ? { onIdleShutdown: opts.onIdleShutdown } : {}),
...(opts.onRpcShutdown ? { onRpcShutdown: opts.onRpcShutdown } : {}),
...(opts.initialAdoptionTestConfig
? { initialAdoptionTestConfig: opts.initialAdoptionTestConfig }
: {})
})
await server.start()
return {
shutdown: () => server.shutdown()
}
}