Files
orca/src/shared/cheap-process-table-snapshot-reader.ts
T
Neilandoutof-place ac5c5ae4cf fix(process-table): pin macOS ps to en_US.UTF-8 so lstart and argv parse under any locale (#33898)
macOS ps formats lstart with the user's locale (%c) and vis-encodes
non-ASCII argv when no UTF-8 locale is set. Under de_DE/en_NZ/ja_JP the
process-table parsers dropped tty/startTime and folded them into command,
breaking agent recognition, pane fingerprints, agent-session start times
and daemon identity. Run those ps readers with LC_ALL=en_US.UTF-8 on
macOS; other platforms keep the inherited env.

Fixes #27004

Co-authored-by: outof-place <contact@outofplace.space>
2026-10-26 03:56:09 -07:00

49 lines
1.7 KiB
TypeScript

import { runProcess } from '@orca/process-host'
import {
CHEAP_PS_ARGS,
PS_MAX_BUFFER_BYTES,
ProcessTableCaptureError,
parseCheapProcessTableRows,
type CheapProcessTableRow
} from './process-table-snapshot'
import {
PS_TIMEOUT_MS,
createProcessTableSnapshotReader,
withEvidenceBudget
} from './process-table-snapshot-reader'
import { psLocaleEnvironment } from './ps-locale-environment'
/** Keep cheap captures separate: without tty=/command= they parse differently and cannot serve strict evidence readers. Share coalescing/TTL policy. */
const cheapProcessTableReader = createProcessTableSnapshotReader<CheapProcessTableRow[]>({
runPs: async () => {
const result = await runProcess({
program: 'ps',
args: CHEAP_PS_ARGS,
env: psLocaleEnvironment(),
timeoutMs: PS_TIMEOUT_MS,
maxOutputBytes: PS_MAX_BUFFER_BYTES
})
// A ceiling hit is truncation, not absence: name it in the domain vocabulary.
if (result.outputTruncated) {
throw new ProcessTableCaptureError('capture_truncated')
}
if (result.timedOut) {
throw new ProcessTableCaptureError('capture_timeout')
}
if (result.code !== 0) {
throw new ProcessTableCaptureError(`ps_exit_${result.code ?? result.signal ?? 'unknown'}`)
}
return parseCheapProcessTableRows(result.stdout)
},
now: () => Date.now()
})
/** Bound stalled cheap reads so the full path can handle fallback instead of pinning a poll tick. */
export async function getCheapProcessTableSnapshot(): Promise<CheapProcessTableRow[]> {
return withEvidenceBudget(cheapProcessTableReader.getSnapshot())
}
export function resetCheapProcessTableSnapshotForTests(): void {
cheapProcessTableReader.reset()
}