mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
fix(terminal): treat macOS '??' tty as no terminal in spawn-file and tmux readers (#23251)
The daemon's spawn-file foreground and child-process readers and the tmux client attachment reader compared root.tty against Linux's '?' only, so a macOS root reported as '??' (or '-' on some ps builds) was read as having a controlling terminal. All three now use the shared hasControllingTty predicate from src/shared/posix-controlling-terminal.ts. Fixes #23245 Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ProcessTableRow } from '../../shared/process-table-snapshot'
|
||||
import type * as SnapshotReader from '../../shared/process-table-snapshot-reader'
|
||||
import { createDaemonPtySubprocessHandle } from './pty-subprocess/subprocess-handle'
|
||||
import { inspectSpawnFileChildProcessesFromRows } from './pty-subprocess/spawn-file-child-processes'
|
||||
import { resolveSpawnFileForegroundFromRows } from './pty-subprocess/spawn-file-foreground-process'
|
||||
import { inspectTerminalHostProcess } from './terminal-host-process-inspection'
|
||||
import { Session } from './session'
|
||||
@@ -226,6 +227,35 @@ it('ignores stopped/background children and another terminal beneath the same ro
|
||||
})
|
||||
})
|
||||
|
||||
// ps spells "no controlling terminal" as `?` on Linux, `??` on macOS and `-` on some builds.
|
||||
describe.each(['??', '?', '-'] as const)('root on no controlling terminal (tty=%s)', (tty) => {
|
||||
const detachedRoot: ProcessTableRow = {
|
||||
pid: 100,
|
||||
ppid: 1,
|
||||
pgid: 100,
|
||||
tpgid: 100,
|
||||
tty,
|
||||
startTime: 'Sat Sep 26 13:44:29 2026',
|
||||
stat: 'S',
|
||||
command: '/bin/zsh'
|
||||
}
|
||||
const detachedTree: ProcessTableRow[] = [
|
||||
detachedRoot,
|
||||
{ ...detachedRoot, pid: 101, ppid: 100, pgid: 100, command: 'claude' }
|
||||
]
|
||||
|
||||
it('declines to name the descendant that shares the group but holds no terminal', () => {
|
||||
expect(resolveSpawnFileForegroundFromRows(detachedTree, 100)).toEqual({
|
||||
available: false,
|
||||
processName: null
|
||||
})
|
||||
})
|
||||
|
||||
it('reports child evidence as unverifiable rather than reading an unfenceable table', () => {
|
||||
expect(inspectSpawnFileChildProcessesFromRows(detachedTree, 100, 'zsh')).toBe('unverifiable')
|
||||
})
|
||||
})
|
||||
|
||||
it('uses Windows job membership and the native process table for ordinary children', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
resolveWindows.mockResolvedValue({ available: true, processName: null })
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
collectDescendantsFromIndex,
|
||||
getProcessTableIndex
|
||||
} from '../../../shared/process-table-index'
|
||||
import { hasControllingTty } from '../../../shared/posix-controlling-terminal'
|
||||
import type { ProcessTableRow } from '../../../shared/process-table-snapshot'
|
||||
import type { PtyChildProcessVerdict } from '../../../shared/terminal-process-inspection'
|
||||
import { readWindowsPtyJobProcessIds } from '../../providers/windows-pty-job-membership'
|
||||
@@ -29,7 +30,7 @@ export function inspectSpawnFileChildProcessesFromRows(
|
||||
): PtyChildProcessVerdict {
|
||||
const index = getProcessTableIndex(rows)
|
||||
const root = index.byPid.get(rootPid)
|
||||
if (!root || !shellName || !root.tty || root.tty === '?') {
|
||||
if (!root || !shellName || !hasControllingTty(root.tty)) {
|
||||
return 'unverifiable'
|
||||
}
|
||||
const tree = [{ ...root, depth: 0 }, ...collectDescendantsFromIndex(index, rootPid)]
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
collectDescendantsFromIndex,
|
||||
getProcessTableIndex
|
||||
} from '../../../shared/process-table-index'
|
||||
import { hasControllingTty } from '../../../shared/posix-controlling-terminal'
|
||||
import type { ProcessTableRow } from '../../../shared/process-table-snapshot'
|
||||
import {
|
||||
getFreshProcessTableSnapshot,
|
||||
@@ -52,7 +53,7 @@ export function resolveSpawnFileForegroundFromRows(
|
||||
): AgentForegroundProcessResolution {
|
||||
const index = getProcessTableIndex(rows)
|
||||
const root = index.byPid.get(rootPid)
|
||||
if (!root || !root.tpgid || root.tpgid < 0 || !root.tty || root.tty === '?') {
|
||||
if (!root || !root.tpgid || root.tpgid < 0 || !hasControllingTty(root.tty)) {
|
||||
return { available: false, processName: null }
|
||||
}
|
||||
const tree = [{ ...root, depth: 0 }, ...collectDescendantsFromIndex(index, rootPid)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { hasControllingTty } from './posix-controlling-terminal'
|
||||
import type { ProcessTableRow } from './process-table-snapshot'
|
||||
|
||||
export type TmuxHookPane = { socket: string; pane: string }
|
||||
@@ -51,7 +52,7 @@ export function resolveTmuxClientAttachment(
|
||||
): TmuxAttachedClient | null {
|
||||
const byPid = new Map(rows.map((row) => [row.pid, row]))
|
||||
const root = byPid.get(rootPid)
|
||||
if (!root?.startTime || !root.tty || root.tty === '?' || !root.tpgid || root.tpgid <= 0) {
|
||||
if (!root?.startTime || !hasControllingTty(root.tty) || !root.tpgid || root.tpgid <= 0) {
|
||||
return null
|
||||
}
|
||||
const attached = clients.filter((client) => {
|
||||
|
||||
Reference in New Issue
Block a user