diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f7100b579a..33a8614eb0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ - Added privacy-safe pseudonymous product analytics for onboarding, command use, execution modes, run outcomes, TTFT, latency, usage, tools, retries, and compactions, with disclosure and opt-out controls ([ENG-4682](https://linear.app/primeintellect/issue/ENG-4682/add-privacy-safe-posthog-analytics-to-prime-agent)). - Changed sent agent messages in the IPython cell UI to show only the message text with a `╰─` gutter when expanded, matching received messages, and hid the raw `agent_message.send` receipt dictionary. - Fixed Homebrew installs attempting to self-update their versioned Cellar keg instead of directing users to `brew upgrade prime-agent` ([#844](https://github.com/PrimeIntellect-ai/prime-agent/issues/844)) +- Fixed macOS timezone changes invalidating the running daemon's process identity ([#879](https://github.com/PrimeIntellect-ai/prime-agent/issues/879)). ## [0.7.1] - 2026-08-07 diff --git a/packages/coding-agent/src/core/session-lease.ts b/packages/coding-agent/src/core/session-lease.ts index 6c4e2975cf..2b23832fa2 100644 --- a/packages/coding-agent/src/core/session-lease.ts +++ b/packages/coding-agent/src/core/session-lease.ts @@ -110,12 +110,17 @@ function isProcessAlive(pid: number): boolean { } } -type ProcessQuery = (command: string, args: string[]) => string; +interface ProcessQueryOptions { + env?: NodeJS.ProcessEnv; +} + +type ProcessQuery = (command: string, args: string[], options?: ProcessQueryOptions) => string; -function runProcessQuery(command: string, args: string[]): string { +function runProcessQuery(command: string, args: string[], options?: ProcessQueryOptions): string { return execFileSync(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], + env: options?.env, }); } @@ -137,6 +142,20 @@ export function getWindowsProcessStartId(pid: number, query: ProcessQuery = runP } } +export function getPsProcessStartId(pid: number, query: ProcessQuery = runProcessQuery): string | undefined { + if (!Number.isInteger(pid) || pid <= 0) { + return undefined; + } + try { + const startTime = query("ps", ["-p", String(pid), "-o", "lstart="], { + env: { ...process.env, TZ: "UTC", LC_ALL: "C" }, + }).trim(); + return startTime ? `ps:${startTime}` : undefined; + } catch { + return undefined; + } +} + export function getProcessStartId(pid: number): string | undefined { if (!Number.isInteger(pid) || pid <= 0) { return undefined; @@ -155,12 +174,7 @@ export function getProcessStartId(pid: number): string | undefined { } catch { // Fall through to the portable process listing used on macOS and BSD. } - try { - const startTime = runProcessQuery("ps", ["-p", String(pid), "-o", "lstart="]).trim(); - return startTime ? `ps:${startTime}` : undefined; - } catch { - return undefined; - } + return getPsProcessStartId(pid); } let currentProcessStartId: string | undefined; diff --git a/packages/coding-agent/test/daemon-ps.test.ts b/packages/coding-agent/test/daemon-ps.test.ts index 6233a9353e..96cd524ada 100644 --- a/packages/coding-agent/test/daemon-ps.test.ts +++ b/packages/coding-agent/test/daemon-ps.test.ts @@ -15,7 +15,7 @@ import { sortDaemons, verifyHelloSupervisorPid, } from "../src/cli/daemon-ps.js"; -import { getProcessStartId } from "../src/core/session-lease.js"; +import { getProcessStartId, getPsProcessStartId } from "../src/core/session-lease.js"; import { defaultDaemonSocketDir } from "../src/modes/daemon/daemon-socket.js"; describe("worker socket classification", () => { @@ -116,6 +116,38 @@ describe("verifyHelloSupervisorPid", () => { }); }); +describe("getPsProcessStartId", () => { + it("uses a stable timezone and locale for the portable process identity", () => { + let queryEnvironment: NodeJS.ProcessEnv | undefined; + const result = getPsProcessStartId(1234, (_command, _args, options) => { + queryEnvironment = options?.env; + return "Sat Aug 8 06:00:00 2026\n"; + }); + + expect(result).toBe("ps:Sat Aug 8 06:00:00 2026"); + expect(queryEnvironment).toMatchObject({ TZ: "UTC", LC_ALL: "C" }); + }); + + it("preserves the surrounding environment", () => { + let queryEnvironment: NodeJS.ProcessEnv | undefined; + const previous = process.env.PRIME_AGENT_PROCESS_START_TEST; + process.env.PRIME_AGENT_PROCESS_START_TEST = "preserved"; + try { + getPsProcessStartId(1234, (_command, _args, options) => { + queryEnvironment = options?.env; + return "Sat Aug 8 06:00:00 2026\n"; + }); + expect(queryEnvironment?.PRIME_AGENT_PROCESS_START_TEST).toBe("preserved"); + } finally { + if (previous === undefined) { + delete process.env.PRIME_AGENT_PROCESS_START_TEST; + } else { + process.env.PRIME_AGENT_PROCESS_START_TEST = previous; + } + } + }); +}); + describe("parsePsEtimes", () => { it("maps pid to elapsed seconds", () => { const uptimes = parsePsEtimes(" 1234 86400\n 5678 42\n");