Skip to content

Commit 8949504

Browse files
committed
fix(logging): route computeGlobalCache slow warn through log interceptor
The Cursor SDK console.warns a slow global-cache rebuild diagnostic (computeGlobalCache: slow ctx-..., meta=/totalMs: ...), which leaked raw into the TUI. Extend the existing console.warn interceptor in both the in-process transport and the sidecar child to recognize it and forward it as a structured opencode log with parsed numeric meta. The timestamp token varies in width, so the pattern accepts 2-3 digit hours.
1 parent 1f2600a commit 8949504

5 files changed

Lines changed: 117 additions & 1 deletion

File tree

‎src/provider/cursor-log-intercept.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,34 @@ function matchesKnownSdkWarning(line: string): boolean {
3737
return SDK_WARNING_PREFIXES.some((prefix) => line.startsWith(prefix));
3838
}
3939

40+
/**
41+
* Slow-cache diagnostic the SDK `console.warn`s when global context rebuild
42+
* exceeds its threshold. Observed shape (colors stripped; the leading
43+
* timestamp token varies in width, e.g. `13:04:05.123` vs `113:26:23.106`):
44+
*
45+
* 113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701
46+
*
47+
* The `/` after `meta=` is how the SDK prints its empty context object.
48+
*/
49+
const SLOW_CACHE_WARN_RE =
50+
/^\d{2,3}:\d{2}:\d{2}\.\d{3}\s+WARN\s+(computeGlobalCache: slow .+?)\s+meta=\/?\s*(.+)$/;
51+
52+
export interface ParsedSlowCacheWarn {
53+
message: string;
54+
meta: Record<string, number>;
55+
}
56+
57+
/** Matches one line against the known slow-cache warn shape. */
58+
export function parseSlowCacheWarnLine(
59+
line: string,
60+
): ParsedSlowCacheWarn | undefined {
61+
const match = SLOW_CACHE_WARN_RE.exec(stripAnsi(line));
62+
if (!match) return undefined;
63+
const [, message, meta] = match;
64+
if (!message) return undefined;
65+
return { message: message.trim(), meta: parseCursorLogMeta(meta ?? "") };
66+
}
67+
4068
/** Parses the `meta={key: value, ...}` tail into a plain numeric object. */
4169
export function parseCursorLogMeta(raw: string): Record<string, number> {
4270
const out: Record<string, number> = {};
@@ -109,6 +137,11 @@ export function installCursorLogInterceptor(): void {
109137
pluginLog("warn", line);
110138
return;
111139
}
140+
const slowCache = parseSlowCacheWarnLine(line);
141+
if (slowCache) {
142+
pluginLog("warn", slowCache.message, slowCache.meta);
143+
return;
144+
}
112145
}
113146
warnPassthrough(...(args as Parameters<typeof console.warn>));
114147
};

‎src/sidecar/agent-host.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ const SDK_WARNING_PREFIXES = [
6464
"shell-parser: tree-sitter natives are unavailable in this artifact",
6565
];
6666

67+
// Slow global-cache rebuild diagnostic (timestamp token varies in width,
68+
// e.g. `13:04:05.123` vs `113:26:23.106`; `/` after `meta=` is the SDK
69+
// printing its empty context object).
70+
const SLOW_CACHE_WARN_RE =
71+
/^\d{2,3}:\d{2}:\d{2}\.\d{3}\s+WARN\s+(computeGlobalCache: slow .+?)\s+meta=\/?\s*(.+)$/;
72+
6773
function parseLogMeta(raw) {
6874
const out = {};
6975
for (const part of raw.split(",")) {
@@ -101,6 +107,17 @@ console.warn = (...args) => {
101107
write({ ev: "log", level: "warn", message: line });
102108
return;
103109
}
110+
const slowCache = SLOW_CACHE_WARN_RE.exec(line);
111+
if (slowCache) {
112+
const [, message, meta] = slowCache;
113+
write({
114+
ev: "log",
115+
level: "warn",
116+
message: message.trim(),
117+
meta: parseLogMeta(meta ?? ""),
118+
});
119+
return;
120+
}
104121
}
105122
originalConsoleWarn(...args);
106123
};

‎test/cursor-log-intercept.test.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,44 @@ describe("installCursorLogInterceptor", () => {
118118
passthrough.mockRestore();
119119
});
120120

121+
it("routes the computeGlobalCache slow warn through pluginLog with parsed meta", () => {
122+
const log = vi.fn().mockResolvedValue(undefined);
123+
setLogBridge({ client: { app: { log } } } as never);
124+
125+
const passthrough = vi.spyOn(console, "warn").mockImplementation(() => {});
126+
installCursorLogInterceptor();
127+
128+
console.warn(
129+
"113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701",
130+
);
131+
console.warn("unrelated warning");
132+
133+
expect(log).toHaveBeenCalledTimes(1);
134+
expect(log).toHaveBeenCalledWith({
135+
body: {
136+
service: "opencode-cursor",
137+
level: "warn",
138+
message:
139+
"computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache",
140+
extra: {
141+
totalMs: 1312,
142+
cloudRule: 0,
143+
codebaseRef: 0,
144+
subagents: 416,
145+
cursorRules: 1311,
146+
ruleCount: 701,
147+
},
148+
},
149+
});
150+
151+
resetCursorLogInterceptor();
152+
// The spy is the pre-interceptor console.warn; passthrough calls must
153+
// reach it, but the recognized line must not.
154+
expect(passthrough).toHaveBeenCalledTimes(1);
155+
expect(passthrough).toHaveBeenCalledWith("unrelated warning");
156+
passthrough.mockRestore();
157+
});
158+
121159
it("is idempotent across repeated installs", () => {
122160
installCursorLogInterceptor();
123161
const first = console.log;

‎test/fixtures/fake-cursor-sdk.mjs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
* `options.emitShellParserWarn` -> Agent.create/resume writes the shell-parser
1919
* "tree-sitter natives unavailable" diagnostic to console.warn, as the real
2020
* @cursor/sdk does on first shell parse, plus one unrelated console.warn.
21+
*
22+
* `options.emitSlowCacheWarn` -> Agent.create/resume writes the
23+
* computeGlobalCache "slow" diagnostic to console.warn, as the real
24+
* @cursor/sdk does on slow global-cache rebuilds, plus one unrelated
25+
* console.warn.
2126
*/
2227

2328
function makeAgent(agentId, options) {
@@ -36,6 +41,12 @@ function makeAgent(agentId, options) {
3641
);
3742
console.warn("some unrelated cursor sdk warning");
3843
}
44+
if (options?.emitSlowCacheWarn) {
45+
console.warn(
46+
"113:26:23.106 WARN computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache meta=/totalMs: 1312, cloudRule: 0, codebaseRef: 0, subagents: 416, cursorRules: 1311, ruleCount: 701",
47+
);
48+
console.warn("some unrelated slow-cache warning");
49+
}
3950
return {
4051
agentId,
4152
model: options?.model,

‎test/sidecar.test.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,31 @@ describe("SidecarClient", () => {
160160
const client = makeClient((level, message, meta) => {
161161
logs.push({ level, message, meta });
162162
});
163-
await client.createAgent({ ...CREATE_OPTIONS, emitShellParserWarn: true });
163+
await client.createAgent({
164+
...CREATE_OPTIONS,
165+
emitShellParserWarn: true,
166+
emitSlowCacheWarn: true,
167+
});
164168

165169
expect(logs).toEqual([
166170
{
167171
level: "warn",
168172
message:
169173
"shell-parser: tree-sitter natives are unavailable in this artifact; shell command analysis degrades to parsingFailed",
170174
},
175+
{
176+
level: "warn",
177+
message:
178+
"computeGlobalCache: slow ctx-LocalRequestContextExecutor. rebuildGlobalCache/LocalRequestContextExecutor.computeGlobalCache",
179+
meta: {
180+
totalMs: 1312,
181+
cloudRule: 0,
182+
codebaseRef: 0,
183+
subagents: 416,
184+
cursorRules: 1311,
185+
ruleCount: 701,
186+
},
187+
},
171188
]);
172189
});
173190

0 commit comments

Comments
 (0)