Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Fixed

- Fixed Linux startup prompt construction to read the CPU model from `/proc/cpuinfo` instead of `os.cpus()`, avoiding per-core sysfs frequency probes on many-core hosts ([#4712](https://github.com/can1357/oh-my-pi/issues/4712)).

## [16.3.10] - 2026-07-06

### Changed
Expand Down
35 changes: 34 additions & 1 deletion packages/coding-agent/src/system-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, it } from "bun:test";
import { describe, expect, it, spyOn } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { buildSystemPrompt } from "./system-prompt";

interface ProbeRunResult {
elapsedMs: number;
Expand Down Expand Up @@ -156,3 +157,35 @@ describe.skipIf(process.platform !== "linux")("system prompt GPU probe", () => {
expect(result.childElapsedMs).toBeLessThan(2000);
}, 15_000);
});

describe.skipIf(process.platform !== "linux")("system prompt CPU model", () => {
it("does not call os.cpus while building the workstation block", async () => {
const cpus = spyOn(os, "cpus").mockImplementation(() => [
{
model: "Synthetic Slow CPU",
speed: 0,
times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
},
]);
try {
await buildSystemPrompt({
resolvedCustomPrompt: "Base prompt",
contextFiles: [],
skills: [],
rules: [],
workspaceTree: {
rootPath: import.meta.dir,
rendered: "",
truncated: false,
totalLines: 0,
agentsMdFiles: [],
},
activeRepoContext: null,
});

expect(cpus).not.toHaveBeenCalled();
} finally {
cpus.mockRestore();
}
});
});
32 changes: 24 additions & 8 deletions packages/coding-agent/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ async function getCachedGpu(): Promise<string | undefined> {
await logger.time("getCachedGpu:saveGpuCache", saveGpuCache, { gpu });
return gpu ?? undefined;
}

async function getCpuModel(): Promise<string | undefined> {
if (process.platform !== "linux") return undefined;
try {
const cpuInfo = await Bun.file("/proc/cpuinfo").text();
const match = /^model name\s*:\s*(.+)$/m.exec(cpuInfo);
return match?.[1]?.trim() || undefined;
} catch (error) {
if (!isEnoent(error)) {
logger.debug("Could not read Linux CPU model", { error: String(error) });
}
return undefined;
}
}

/**
* Kernel identity for the workstation block. Prefers the uname build string
* from `os.version()`, but Bun on macOS 15+ (Darwin 24/25) returns the literal
Expand All @@ -263,13 +278,10 @@ function getKernelIdentity(): string {
return `${os.type()} ${os.release()}`.trim();
}

function getEnvironmentInfo(gpu: string | undefined): Array<{ label: string; value: string }> {
let cpuModel: string | undefined;
try {
cpuModel = os.cpus()[0]?.model;
} catch {
cpuModel = undefined;
}
function getEnvironmentInfo(
cpuModel: string | undefined,
gpu: string | undefined,
): Array<{ label: string; value: string }> {
const entries: Array<{ label: string; value: string | undefined }> = [
{ label: "OS", value: `${os.platform()} ${os.release()}` },
{ label: "Distro", value: os.type() },
Expand Down Expand Up @@ -549,6 +561,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
agentsMdFiles: [],
} satisfies WorkspaceTree,
activeRepoContext: null as ActiveRepoContext | null,
cpuModel: undefined as string | undefined,
gpu: undefined as string | undefined,
};

Expand Down Expand Up @@ -619,6 +632,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
providedActiveRepoContext !== undefined
? Promise.resolve(providedActiveRepoContext)
: logger.time("resolveActiveRepoContext", () => resolveActiveRepoContext(resolvedCwd));
const cpuModelPromise = logger.time("getCpuModel", getCpuModel);
const gpuPromise = logger.time("getCachedGpu", getCachedGpu);

const [
Expand All @@ -629,6 +643,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
skills,
workspaceTree,
activeRepoContext,
cpuModel,
gpu,
] = await Promise.all([
withDeadline(
Expand All @@ -652,6 +667,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
withDeadline("loadSkills", skillsPromise, prepDefaults.skills),
withDeadline("buildWorkspaceTree", workspaceTreePromise, prepDefaults.workspaceTree),
withDeadline("resolveActiveRepoContext", activeRepoContextPromise, prepDefaults.activeRepoContext),
withDeadline("getCpuModel", cpuModelPromise, prepDefaults.cpuModel),
withDeadline("getCachedGpu", gpuPromise, prepDefaults.gpu),
]);
clearTimeout(deadlineTimer);
Expand Down Expand Up @@ -732,7 +748,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
];
const injectedAlwaysApplyRules = dedupeAlwaysApplyRules(alwaysApplyRules, promptSources);

const environment = getEnvironmentInfo(gpu);
const environment = getEnvironmentInfo(cpuModel, gpu);
const data = {
systemPromptCustomization: effectiveSystemPromptCustomization,
customPrompt: resolvedCustomPrompt,
Expand Down
Loading