From 605cbd03fb0798ac4c2319a6fb42f0ee06d718ad Mon Sep 17 00:00:00 2001 From: chilung Date: Wed, 16 Sep 2026 08:33:04 +0000 Subject: [PATCH 1/3] perf(usage): guard redundant directory creation and permission checks on append --- src/usage/log.ts | 13 ++++++++++++- tests/usage/usage-log.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/usage/log.ts b/src/usage/log.ts index aadeb1648bb..2ddfa45a7c3 100644 --- a/src/usage/log.ts +++ b/src/usage/log.ts @@ -852,18 +852,27 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { }; } +let ensuredUsageLogDir: string | null = null; +let ensuredUsageLogFile: string | null = null; + function ensureUsageLogDir(): void { const dir = getConfigDir(); + if (ensuredUsageLogDir === dir) return; recordOwnedConfigPath(dir, usageLogPath()); mkdirSync(dir, { recursive: true, mode: 0o700 }); try { chmodSync(dir, 0o700); } catch { /* best-effort on platforms that ignore chmod */ } + ensuredUsageLogDir = dir; } export function appendUsageEntry(entry: PersistedUsageEntry): void { ensureUsageLogDir(); const path = usageLogPath(); + const fileAlreadyEnsured = ensuredUsageLogFile === path; appendFileSync(path, `${JSON.stringify(normalizeUsageEntry(entry))}\n`, { encoding: "utf-8", mode: 0o600 }); - try { chmodSync(path, 0o600); } catch { /* best-effort on platforms that ignore chmod */ } + if (!fileAlreadyEnsured) { + try { chmodSync(path, 0o600); } catch { /* best-effort on platforms that ignore chmod */ } + ensuredUsageLogFile = path; + } } export type UsageLogRevision = { @@ -1057,6 +1066,8 @@ export function resetUsageReadCacheForTests(): void { managementUsageReadInflight?.abort.abort(); managementUsageReadInflight = null; retainedUsageSnapshot = null; + ensuredUsageLogDir = null; + ensuredUsageLogFile = null; } function readExactly(fd: number, length: number, position: number): Buffer | null { diff --git a/tests/usage/usage-log.test.ts b/tests/usage/usage-log.test.ts index 9cd97855a6a..21973e6a8f1 100644 --- a/tests/usage/usage-log.test.ts +++ b/tests/usage/usage-log.test.ts @@ -1012,4 +1012,28 @@ describe("usage log", () => { expect(readRecentUsageEntries(1)).toEqual([]); }, STORE_BUDGET_MS); + + test("appendUsageEntry avoids redundant mkdirSync and chmodSync on consecutive calls", async () => { + const nodeFs = await import("node:fs"); + const mkdirSpy = spyOn(nodeFs, "mkdirSync"); + const chmodSpy = spyOn(nodeFs, "chmodSync"); + + for (let i = 0; i < 5; i++) { + appendUsageEntry({ + requestId: `ocx-perf-${i}`, + timestamp: Date.now(), + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 10, + usageStatus: "unreported", + }); + } + + expect(mkdirSpy.mock.calls.length).toBe(1); + expect(chmodSpy.mock.calls.length).toBeLessThanOrEqual(2); + + mkdirSpy.mockRestore(); + chmodSpy.mockRestore(); + }); }); From 3a002c48e6f47581f1e492d3daf35dd5b392cc98 Mon Sep 17 00:00:00 2001 From: chilung Date: Wed, 16 Sep 2026 09:04:20 +0000 Subject: [PATCH 2/3] fix(usage): recover cached log directory on ENOENT and harden test cleanup Co-authored-by: chilung --- src/usage/log.ts | 26 +++++++++++---- tests/usage/usage-log.test.ts | 63 ++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/usage/log.ts b/src/usage/log.ts index 2ddfa45a7c3..ef2857918e3 100644 --- a/src/usage/log.ts +++ b/src/usage/log.ts @@ -865,13 +865,27 @@ function ensureUsageLogDir(): void { } export function appendUsageEntry(entry: PersistedUsageEntry): void { - ensureUsageLogDir(); + const line = `${JSON.stringify(normalizeUsageEntry(entry))}\n`; const path = usageLogPath(); - const fileAlreadyEnsured = ensuredUsageLogFile === path; - appendFileSync(path, `${JSON.stringify(normalizeUsageEntry(entry))}\n`, { encoding: "utf-8", mode: 0o600 }); - if (!fileAlreadyEnsured) { - try { chmodSync(path, 0o600); } catch { /* best-effort on platforms that ignore chmod */ } - ensuredUsageLogFile = path; + const doAppend = (): void => { + ensureUsageLogDir(); + const fileAlreadyEnsured = ensuredUsageLogFile === path; + appendFileSync(path, line, { encoding: "utf-8", mode: 0o600 }); + if (!fileAlreadyEnsured) { + try { chmodSync(path, 0o600); } catch { /* best-effort on platforms that ignore chmod */ } + ensuredUsageLogFile = path; + } + }; + try { + doAppend(); + } catch (error: any) { + if (error?.code === "ENOENT") { + ensuredUsageLogDir = null; + ensuredUsageLogFile = null; + doAppend(); + return; + } + throw error; } } diff --git a/tests/usage/usage-log.test.ts b/tests/usage/usage-log.test.ts index 21973e6a8f1..58ba3fcc98a 100644 --- a/tests/usage/usage-log.test.ts +++ b/tests/usage/usage-log.test.ts @@ -1013,27 +1013,60 @@ describe("usage log", () => { expect(readRecentUsageEntries(1)).toEqual([]); }, STORE_BUDGET_MS); - test("appendUsageEntry avoids redundant mkdirSync and chmodSync on consecutive calls", async () => { + test("appendUsageEntry avoids redundant mkdirSync and chmodSync on consecutive calls", async () => { const nodeFs = await import("node:fs"); const mkdirSpy = spyOn(nodeFs, "mkdirSync"); const chmodSpy = spyOn(nodeFs, "chmodSync"); - for (let i = 0; i < 5; i++) { - appendUsageEntry({ - requestId: `ocx-perf-${i}`, - timestamp: Date.now(), - provider: "openai", - model: "gpt-4o", - status: 200, - durationMs: 10, - usageStatus: "unreported", - }); + try { + for (let i = 0; i < 5; i++) { + appendUsageEntry({ + requestId: `ocx-perf-${i}`, + timestamp: Date.now(), + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 10, + usageStatus: "unreported", + }); + } + + expect(mkdirSpy.mock.calls.length).toBe(1); + expect(chmodSpy.mock.calls.length).toBeLessThanOrEqual(2); + } finally { + mkdirSpy.mockRestore(); + chmodSpy.mockRestore(); } + }); + + test("appendUsageEntry recovers cleanly on ENOENT if usage directory is deleted between calls", () => { + const entry1: PersistedUsageEntry = { + requestId: "ocx-enoent-1", + timestamp: Date.now(), + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 10, + usageStatus: "unreported", + }; + appendUsageEntry(entry1); - expect(mkdirSpy.mock.calls.length).toBe(1); - expect(chmodSpy.mock.calls.length).toBeLessThanOrEqual(2); + // Simulate directory deletion by log rotation / cleanup while process is running + rmSync(testDir, { recursive: true, force: true }); + expect(existsSync(testDir)).toBe(false); - mkdirSpy.mockRestore(); - chmodSpy.mockRestore(); + const entry2: PersistedUsageEntry = { + requestId: "ocx-enoent-2", + timestamp: Date.now(), + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 12, + usageStatus: "unreported", + }; + expect(() => appendUsageEntry(entry2)).not.toThrow(); + const readBack = readRecentUsageEntries(10); + expect(readBack.length).toBe(1); + expect(readBack[0].requestId).toBe("ocx-enoent-2"); }); }); From 29ef1b7f901f9d827c5cdd8c6887103ec8b5327f Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 18 Sep 2026 08:21:15 +0900 Subject: [PATCH 3/3] fix(usage): bound permission hardening cache Keep consecutive appends off redundant filesystem hardening while reapplying owner-only modes after a one-second cache window. Co-authored-by: chilung --- src/usage/log.ts | 38 +++++++++++++++++------ structure/gui-and-management-api.md | 7 ++++- structure/runtime.md | 5 ++- tests/usage/usage-log.test.ts | 47 +++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/usage/log.ts b/src/usage/log.ts index ef2857918e3..523f1c209d3 100644 --- a/src/usage/log.ts +++ b/src/usage/log.ts @@ -852,28 +852,48 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { }; } -let ensuredUsageLogDir: string | null = null; -let ensuredUsageLogFile: string | null = null; +// Bound hot-path filesystem hardening to once per second while ensuring an external mode +// widening cannot suppress write-triggered repair for the lifetime of the process. +const USAGE_LOG_PERMISSION_RECHECK_MS = 1_000; -function ensureUsageLogDir(): void { +type UsageLogPermissionCheck = { + path: string; + checkedAt: number; +}; + +let ensuredUsageLogDir: UsageLogPermissionCheck | null = null; +let ensuredUsageLogFile: UsageLogPermissionCheck | null = null; + +function usageLogPermissionCheckIsCurrent( + check: UsageLogPermissionCheck | null, + path: string, + now: number, +): boolean { + return check?.path === path + && now >= check.checkedAt + && now - check.checkedAt < USAGE_LOG_PERMISSION_RECHECK_MS; +} + +function ensureUsageLogDir(now: number): void { const dir = getConfigDir(); - if (ensuredUsageLogDir === dir) return; + if (usageLogPermissionCheckIsCurrent(ensuredUsageLogDir, dir, now)) return; recordOwnedConfigPath(dir, usageLogPath()); mkdirSync(dir, { recursive: true, mode: 0o700 }); try { chmodSync(dir, 0o700); } catch { /* best-effort on platforms that ignore chmod */ } - ensuredUsageLogDir = dir; + ensuredUsageLogDir = { path: dir, checkedAt: now }; } export function appendUsageEntry(entry: PersistedUsageEntry): void { const line = `${JSON.stringify(normalizeUsageEntry(entry))}\n`; const path = usageLogPath(); + const now = Date.now(); const doAppend = (): void => { - ensureUsageLogDir(); - const fileAlreadyEnsured = ensuredUsageLogFile === path; + ensureUsageLogDir(now); + const filePermissionsCurrent = usageLogPermissionCheckIsCurrent(ensuredUsageLogFile, path, now); appendFileSync(path, line, { encoding: "utf-8", mode: 0o600 }); - if (!fileAlreadyEnsured) { + if (!filePermissionsCurrent) { try { chmodSync(path, 0o600); } catch { /* best-effort on platforms that ignore chmod */ } - ensuredUsageLogFile = path; + ensuredUsageLogFile = { path, checkedAt: now }; } }; try { diff --git a/structure/gui-and-management-api.md b/structure/gui-and-management-api.md index 5d992a10468..edbce50fb38 100644 --- a/structure/gui-and-management-api.md +++ b/structure/gui-and-management-api.md @@ -464,7 +464,12 @@ status, so an unexpected management response cannot add raw upstream material. > Decision record: [ADR-0078](decisions/ADR-0078-usage-accounting.md) -`src/usage/log.ts` writes append-only JSONL to `~/.opencodex/usage.jsonl` with file mode `0o600`. +`src/usage/log.ts` writes append-only JSONL to `~/.opencodex/usage.jsonl` with file mode `0o600` +inside an owner-only `0o700` directory. Consecutive appends reuse the directory and permission +check for at most one second; the first append at or after that boundary attempts to reapply both +modes, and an `ENOENT` append invalidates the cache and recreates the path immediately. This is a +bounded, write-triggered repair of externally widened POSIX modes, not continuous filesystem +monitoring or protection against another process changing the path again after the check. An opt-in shadow-call rewrite persists the bounded, redacted original helper model as `shadowCallRewrittenFrom`, so helper traffic remains identifiable after restart without storing request content or inferring a helper subtype from timing. diff --git a/structure/runtime.md b/structure/runtime.md index ed2bffbf75f..94a45b56df3 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -405,7 +405,10 @@ fingerprint mismatch are named separately rather than all reported as a missing Codex display-cache expiry, retained main-policy evidence, and reset history follow the [quota cache contract](providers/openai-tiers.md#quota-cache-and-short-window-history). -Usage consumers preserve positive incomplete-history metadata as specified in [usage accounting](gui-and-management-api.md#usage-accounting); readable totals are not represented as a complete ledger. +Usage consumers preserve positive incomplete-history metadata as specified in +[usage accounting](gui-and-management-api.md#usage-accounting); readable totals are not represented +as a complete ledger. The same contract owns `src/usage/log.ts` append-path permission rechecks and +their bounded cache. Connected `ocx usage` reads `/v1/usage` through `src/client/hub-client.ts`, using its enrolled data key and checking connection/token ownership before and after the read. It reports hub/client scope and never substitutes local totals on failure. Standalone commands retain their management endpoint. diff --git a/tests/usage/usage-log.test.ts b/tests/usage/usage-log.test.ts index 58ba3fcc98a..536f4c446f5 100644 --- a/tests/usage/usage-log.test.ts +++ b/tests/usage/usage-log.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"; import { STORE_BUDGET_MS } from "../helpers/test-budget"; -import { closeSync, existsSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, truncateSync, writeFileSync, writeSync } from "node:fs"; +import { chmodSync, closeSync, existsSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, truncateSync, writeFileSync, writeSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { @@ -1017,6 +1017,10 @@ describe("usage log", () => { const nodeFs = await import("node:fs"); const mkdirSpy = spyOn(nodeFs, "mkdirSync"); const chmodSpy = spyOn(nodeFs, "chmodSync"); + // Pinned so the count measures the cache and not the runner's scheduling: five + // appends that happen to straddle the one-second boundary would legitimately + // harden twice, and a saturated CI runner can take that long. + const clock = spyOn(Date, "now").mockReturnValue(1_700_000_000_000); try { for (let i = 0; i < 5; i++) { @@ -1032,13 +1036,52 @@ describe("usage log", () => { } expect(mkdirSpy.mock.calls.length).toBe(1); - expect(chmodSpy.mock.calls.length).toBeLessThanOrEqual(2); + // One for the directory, one for the file. Not five. + expect(chmodSpy.mock.calls.length).toBe(2); } finally { + clock.mockRestore(); mkdirSpy.mockRestore(); chmodSpy.mockRestore(); } }); + test.skipIf(process.platform === "win32")( + "appendUsageEntry re-narrows externally widened permissions after the bounded cache window", + () => { + const now = Date.now(); + const clock = spyOn(Date, "now").mockReturnValue(now); + try { + appendUsageEntry({ + requestId: "ocx-permission-initial", + timestamp: now, + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 10, + usageStatus: "unreported", + }); + chmodSync(testDir, 0o755); + chmodSync(usageLogPath(), 0o644); + + clock.mockReturnValue(now + 1_000); + appendUsageEntry({ + requestId: "ocx-permission-recheck", + timestamp: now + 1_000, + provider: "openai", + model: "gpt-4o", + status: 200, + durationMs: 11, + usageStatus: "unreported", + }); + + expect(statSync(testDir).mode & 0o777).toBe(0o700); + expect(statSync(usageLogPath()).mode & 0o777).toBe(0o600); + } finally { + clock.mockRestore(); + } + }, + ); + test("appendUsageEntry recovers cleanly on ENOENT if usage directory is deleted between calls", () => { const entry1: PersistedUsageEntry = { requestId: "ocx-enoent-1",