Files
orca/src/main/daemon/daemon-entry.test.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

141 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { parseArgs } from './daemon-entry'
describe('daemon-entry parseArgs', () => {
it('parses --socket and --token flags', () => {
const result = parseArgs(['--socket', '/tmp/test.sock', '--token', '/tmp/test.token'])
expect(result).toEqual({
socketPath: '/tmp/test.sock',
tokenPath: '/tmp/test.token'
})
})
it('handles flags in any order', () => {
const result = parseArgs(['--token', '/tmp/t.token', '--socket', '/tmp/t.sock'])
expect(result).toEqual({
socketPath: '/tmp/t.sock',
tokenPath: '/tmp/t.token'
})
})
it('throws when --socket is missing', () => {
expect(() => parseArgs(['--token', '/tmp/t.token'])).toThrow('Usage:')
})
it('throws when --token is missing', () => {
expect(() => parseArgs(['--socket', '/tmp/t.sock'])).toThrow('Usage:')
})
it('throws with no args', () => {
expect(() => parseArgs([])).toThrow('Usage:')
})
it('omits logFilePath when --log-file is absent (adopted old daemons)', () => {
const result = parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token'])
expect(result).not.toHaveProperty('logFilePath')
})
it('parses --log-file when present', () => {
const result = parseArgs([
'--socket',
'/tmp/t.sock',
'--token',
'/tmp/t.token',
'--log-file',
'/tmp/daemon.log'
])
expect(result).toEqual({
socketPath: '/tmp/t.sock',
tokenPath: '/tmp/t.token',
logFilePath: '/tmp/daemon.log'
})
})
it('parses the internal PID-record ownership pair', () => {
expect(
parseArgs([
'--socket',
'/tmp/t.sock',
'--token',
'/tmp/t.token',
'--pid-record',
'/tmp/t.pid',
'--launch-nonce',
'launch-a'
])
).toEqual({
socketPath: '/tmp/t.sock',
tokenPath: '/tmp/t.token',
pidPath: '/tmp/t.pid',
launchNonce: 'launch-a'
})
})
it('parses PID publication metadata from the launcher', () => {
expect(
parseArgs([
'--socket',
'/tmp/t.sock',
'--token',
'/tmp/t.token',
'--pid-record',
'/tmp/t.pid',
'--launch-nonce',
'launch-a',
'--entry-path',
'/app/daemon-entry.js',
'--app-version',
'1.2.3',
'--spawner-exec-path',
'/Applications/Orca.app/Contents/MacOS/Orca'
])
).toMatchObject({
pidPath: '/tmp/t.pid',
launchNonce: 'launch-a',
entryPath: '/app/daemon-entry.js',
appVersion: '1.2.3',
spawnerExecPath: '/Applications/Orca.app/Contents/MacOS/Orca'
})
})
it('rejects either PID-record ownership argument without its pair', () => {
expect(() =>
parseArgs([
'--socket',
'/tmp/t.sock',
'--token',
'/tmp/t.token',
'--pid-record',
'/tmp/t.pid'
])
).toThrow('provided together')
expect(() =>
parseArgs([
'--socket',
'/tmp/t.sock',
'--token',
'/tmp/t.token',
'--launch-nonce',
'launch-a'
])
).toThrow('provided together')
})
it('still requires --socket and --token when --log-file is given', () => {
expect(() => parseArgs(['--log-file', '/tmp/daemon.log'])).toThrow('Usage:')
})
it('parses the GUI-only --login-session-watch flag and omits it when absent', () => {
expect(
parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token', '--login-session-watch'])
).toEqual({
socketPath: '/tmp/t.sock',
tokenPath: '/tmp/t.token',
loginSessionWatch: true
})
expect(parseArgs(['--socket', '/tmp/t.sock', '--token', '/tmp/t.token'])).not.toHaveProperty(
'loginSessionWatch'
)
})
})