Repository navigation
fix(process-table): read ps under a pinned locale - #27005
outof-place wants to merge 1 commit into
Conversation
macOS ps prints lstart with the locale's %c, so en_NZ gives "Sat 10 Oct 00:04:51 2026" where the parsers expect "Sat Oct 10 ...". The full capture then folds tty and the start time into command, which hides every agent and stops pane fingerprints from forming. Date.parse rejects most localized forms too, which breaks the agent-session and daemon identity probes. With no locale at all, ps instead mangles non-ASCII argv into vis(3) meta notation. psLocaleEnvironment gives every ps whose lstart or command Orca parses one uniform en_US.UTF-8 on macOS. A lone LC_TIME=C does not work there: LC_ALL overrides it, and ps falls back to the C character set once categories differ. On Linux, glibc honors LC_TIME=C; procps keeps the field order but localizes the names, and LC_ALL is moved to LC_CTYPE so it cannot override LC_TIME. Call sites that already pin LC_ALL=C are unchanged. Fixes #27004 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe change adds Fixed issue severity: Medium Priority: ➖ Normal Merge Risk: 🔵 Low · up to The locale change appears mergeable with a small test-cleanup fix: restore an absent LANG variable by deleting it so later tests inherit the intended environment. Pre-merge checks |
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
2090775b-3f12-40c2-87f5-421205651133
📒 Files selected for processing (9)
src/main/daemon/daemon-process-identity-query.tssrc/main/daemon/daemon-process-identity-time-zone.test.tssrc/main/runtime/agent-session-process-identity-probe.tssrc/main/runtime/terminal-foreground-group.tssrc/shared/cheap-process-table-snapshot-reader.tssrc/shared/process-table-snapshot-locale.test.tssrc/shared/process-table-snapshot-reader.tssrc/shared/ps-locale-environment.test.tssrc/shared/ps-locale-environment.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
| beforeEach(() => { | ||
| Object.defineProperty(process, 'platform', { value: 'darwin' }) | ||
| process.env.LANG = 'en_NZ.UTF-8' | ||
| execFileMock.mockReset() | ||
| resetProcessTableSnapshotForTests() | ||
| }) | ||
| afterEach(() => { | ||
| Object.defineProperty(process, 'platform', platform) | ||
| process.env.LANG = lang | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restore LANG correctly when it was originally unset.
Line 32 assigns process.env.LANG = lang. If LANG was unset, lang is undefined. Node then stores the string "undefined" in process.env. This pollutes the environment for later tests in the same worker. Delete the key when the original value is undefined.
Proposed fix
afterEach(() => {
Object.defineProperty(process, 'platform', platform)
- process.env.LANG = lang
+ if (lang === undefined) {
+ delete process.env.LANG
+ } else {
+ process.env.LANG = lang
+ }
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| beforeEach(() => { | |
| Object.defineProperty(process, 'platform', { value: 'darwin' }) | |
| process.env.LANG = 'en_NZ.UTF-8' | |
| execFileMock.mockReset() | |
| resetProcessTableSnapshotForTests() | |
| }) | |
| afterEach(() => { | |
| Object.defineProperty(process, 'platform', platform) | |
| process.env.LANG = lang | |
| }) | |
| beforeEach(() => { | |
| Object.defineProperty(process, 'platform', { value: 'darwin' }) | |
| process.env.LANG = 'en_NZ.UTF-8' | |
| execFileMock.mockReset() | |
| resetProcessTableSnapshotForTests() | |
| }) | |
| afterEach(() => { | |
| Object.defineProperty(process, 'platform', platform) | |
| if (lang === undefined) { | |
| delete process.env.LANG | |
| } else { | |
| process.env.LANG = lang | |
| } | |
| }) |
|
Thanks so much for this fix, @outof-place! I couldn't push to your fork's branch, so I rebased it onto current main, tightened it up a little (macOS-only |
ELI5
Orca finds out what runs in each terminal by asking the
pstool and reading its printout.psprints dates the way the user's language settings say, so on a Mac set to New Zealand, German or Japanese English the dates come out in a different order or language. Orca's reader then lost track of which column was which and stopped recognizing agents. With no language setting at all,psgarbled non-English characters in program arguments instead. Orca now tellspsexactly which locale to print in, so the output always has the shape Orca reads.What Changed
Before: with
LANGorLC_*set to a non-US locale, the macOS full process capture read every row wrong. Under en_NZ,ps -o lstart=printsSat 10 Oct 00:04:51 2026rather thanSat Oct 10 …. The parser's start-time pattern then fails, and the row falls through to a pattern that puts the terminal name and the start time intocommand. A real row read like this:command: '?? Fri 9 Oct 16:56:02 2026 /Applications/Orca.app/…', with nottyand nostartTime. Consequences:??orttys003as the program, so no agent was found in the full capture.Date.parsereturnedNaNfor most localized forms (Sa. 10 Okt.,土 10/10), which broke the agent-session start-time probe and the daemon pid identity check.Orca gets a locale whenever it starts from a terminal:
pnpm dev,openfrom a shell, or a relay on an SSH host with a forwardedLANG. With no locale at all (a Dock-launched Orca),psused the C locale and printed non-ASCII argv in vis(3) meta notation, sox-ł-日本read asx-M-EM^B-M-fM^WM-%M-fM^\M-,.After: every
pswhoselstart=orcommand=Orca parses gets a pinned locale. Start times always readSat Oct 10 00:04:51 2026, and argv keeps its real UTF-8.How: a new
src/shared/ps-locale-environment.tsbuilds the environment, used by:src/shared/process-table-snapshot-reader.ts)src/shared/cheap-process-table-snapshot-reader.ts)src/main/runtime/terminal-foreground-group.ts)src/main/runtime/agent-session-process-identity-probe.ts)src/main/daemon/daemon-process-identity-query.ts)Per platform:
LANGand everyLC_*, then setLC_ALL=en_US.UTF-8.LC_TIME=C. A user'sLC_ALLis moved toLC_CTYPE, because it would otherwise overrideLC_TIMEand its character set still has to apply.Call sites that already pin
LC_ALL=C, such as the hook identity capture or the UTC daemon identity, are untouched. Their strings already have this shape.Why
macOS needs a uniform locale. I ran macOS
ps -o lstart=,command=on a process withx-ł-日本in its argv under eight user locale setups:LANG=en_NZ,LANG=de_DE,LANG=ja_JP,LANG=CLC_ALL=en_NZLANG+LC_TIME+LC_CTYPEmixedLC_ALL+LC_TIME+LC_MESSAGESmixedResults:
LC_TIME=Calone fails wheneverLC_ALLis set, and still mangles argv when nothing else is set. macOSpsfalls back to the C character set as soon as the categories differ.LC_ALL=en_US.UTF-8gave a USlstartand intact argv in all eight.en_US.UTF-8ships with every macOS.Linux can keep the user's character set. In Debian with procps-ng 4.0.4,
lstartkeeps its field order but localizes the names (Fr Okt 9 22:08:59 2026under de_DE).LC_ALL=Creplaces non-ASCII argv with?. glibc honors a loneLC_TIME=C, so Linux gets English names while keeping the user's character set.Alternatives considered:
%c. Japanese and Polish orders differ, andDate.parserejects most of them.pshas no locale-independent start-time column:etimechanges every second andstartis coarser.psfallback and for Linux.Linked Issue
Fixes #27004
Visual Proof
N/A: no UI change. The difference is which agents the panes and sidebar recognize when Orca runs with a non-US locale.
Testing
Tested on macOS 27 (M4 Max). Linux procps behavior was checked in Docker (
debian:stable-slim, procps-ng 4.0.4, de_DE/en_NZ/en_US generated).src/shared/process-table-snapshot-locale.test.ts: the regression test with an en_NZ fixture.ttyandstartTimeare lost andcommandstarts with the terminal.psmocked to answer in en_NZ unless the pinned locale is passed, returns the righttty,startTimeand agent command whileLANG=en_NZ.UTF-8.src/shared/ps-locale-environment.test.ts:LC_ALL→LC_CTYPEwithLC_TIME=C, Windows untouched.pswith en_NZ, ja_JP, mixedLC_ALL/LC_TIMEand no locale. Each case must give aDate.parse-able USlstartand argv equal toorca-ł-日本 30.daemon-process-identity-time-zone.test.ts: the local-time daemon identity now passes the pinned environment.Commands run:
pnpm tc, every test file that touches the changed modules (35 files, 412 passing), oxlint, oxfmt, the max-lines ratchet, andORCA_CODE_QUALITY_BASE=upstream/main pnpm run check:code-quality:changed.Not tested by hand: Linux beyond the Docker check of procps itself, and Windows, which has no
psand keeps its environment as is.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Claude Code (Claude Opus 5.5) found the bug while checking
psparity for #26985, wrote the fix, the tests and this description, and ran the verification above.Review
Start with
src/shared/ps-locale-environment.ts; each call site only addsenv: psLocaleEnvironment().Agent skill upstream boundary
Notes
ps.LANGon an SSH host no longer breaks them.LC_ALL=Ccall sites produce, so comparisons with them keep holding.TZis not touched.lstartin the C locale to match this.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred). Typecheck, lint on the changed files, the quality gate and the affected test files pass locally; CI covers the full suite.🤖 Generated with Claude Code