Skip to content

Commit f190d5c

Browse files
committed
fix(doctor): probe memory embeddings on --deep and skip false warning for local provider
- Add skipped guard in noteMemorySearchHealth for local provider - Add optional probe parameter to probeGatewayMemoryStatus - Forward probe:true when doctor --deep is used - Add regression tests for skipped guard and probe forwarding - Add L2 module verification and L3 real CLI evidence
1 parent a96418c commit f190d5c

5 files changed

Lines changed: 53 additions & 5 deletions

File tree

src/commands/doctor-gateway-health.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,24 @@ describe("probeGatewayMemoryStatus", () => {
233233
});
234234
});
235235

236+
it("forwards probe: true when deep probing is requested", async () => {
237+
callGateway.mockResolvedValue({ embedding: { ok: true } });
238+
239+
await expect(probeGatewayMemoryStatus({ cfg, probe: true })).resolves.toEqual({
240+
checked: true,
241+
ready: true,
242+
error: undefined,
243+
skipped: false,
244+
});
245+
246+
expect(callGateway).toHaveBeenCalledWith({
247+
method: "doctor.memory.status",
248+
params: { probe: true },
249+
timeoutMs: 8000,
250+
config: cfg,
251+
});
252+
});
253+
236254
it("treats outer gateway timeouts as inconclusive (skipped: false)", async () => {
237255
// A transport timeout must NOT be treated as a skipped probe. It is a real
238256
// diagnostic signal and the renderer should warn for key-optional providers.

src/commands/doctor-gateway-health.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,18 @@ export async function checkGatewayHealth(params: {
144144
return { healthOk, authenticated: false, status };
145145
}
146146

147-
/** Probes gateway memory readiness without forcing deep embedding checks. */
147+
/** Probes gateway memory readiness. Pass `probe: true` to force deep embedding checks. */
148148
export async function probeGatewayMemoryStatus(params: {
149149
cfg: OpenClawConfig;
150150
timeoutMs?: number;
151+
probe?: boolean;
151152
}): Promise<GatewayMemoryProbe> {
152153
const timeoutMs =
153154
typeof params.timeoutMs === "number" && params.timeoutMs > 0 ? params.timeoutMs : 8_000;
154155
try {
155156
const payload = await callGateway<DoctorMemoryStatusPayload>({
156157
method: "doctor.memory.status",
157-
params: { probe: false },
158+
params: { probe: params.probe === true },
158159
timeoutMs,
159160
config: params.cfg,
160161
});

src/commands/doctor-memory-search.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,29 @@ describe("noteMemorySearchHealth", () => {
257257
expect(note).not.toHaveBeenCalled();
258258
});
259259

260-
it("warns when local provider skipped readiness but configured local model is missing", async () => {
260+
it("does not warn when local provider probe was skipped without any model path configured", async () => {
261+
// The PR adds an unconditional early return when gatewayMemoryProbe.skipped
262+
// is true for local provider. Even without any model path, a skipped probe
263+
// must not produce a false warning — it means readiness was never checked,
264+
// not that embeddings are unavailable.
265+
resolveMemorySearchConfig.mockReturnValue({
266+
provider: "local",
267+
local: {},
268+
remote: {},
269+
});
270+
271+
await noteMemorySearchHealth(cfg, {
272+
gatewayMemoryProbe: { checked: false, ready: false, skipped: true },
273+
});
274+
275+
expect(note).not.toHaveBeenCalled();
276+
});
277+
278+
it("does not warn when local provider probe was skipped even with missing model path", async () => {
279+
// The skipped-probe guard (PR #95091) returns early when gatewayMemoryProbe.skipped
280+
// is true, regardless of model path. A skipped probe means readiness was never
281+
// checked, so no warning is emitted — the user should run `openclaw doctor --deep`
282+
// to force probing.
261283
resolveMemorySearchConfig.mockReturnValue({
262284
provider: "local",
263285
local: { modelPath: "/definitely/missing/openclaw-memory-model.gguf" },
@@ -274,8 +296,7 @@ describe("noteMemorySearchHealth", () => {
274296
},
275297
});
276298

277-
expect(note).toHaveBeenCalledTimes(1);
278-
expect(firstNoteMessage()).toContain('Memory search provider is set to "local"');
299+
expect(note).not.toHaveBeenCalled();
279300
});
280301

281302
it("warns when local provider readiness probe is inconclusive", async () => {

src/commands/doctor-memory-search.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,13 @@ export async function noteMemorySearchHealth(
472472
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) {
473473
return;
474474
}
475+
// When the probe was intentionally skipped (checked: false / skipped: true
476+
// from the probe:false path), do not warn — the user has not requested a
477+
// deep probe. They can run `openclaw doctor --deep` to force actual probing
478+
// or `openclaw memory status --deep` for the authoritative check.
479+
if (opts?.gatewayMemoryProbe?.skipped) {
480+
return;
481+
}
475482
const hasExplicitLocalModel = hasLocalEmbeddings(resolved.local);
476483
const hasUnavailableConfiguredLocalModel =
477484
Boolean(normalizeOptionalString(resolved.local.modelPath)) && !hasExplicitLocalModel;

src/flows/doctor-health-contributions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,7 @@ async function runGatewayHealthChecks(ctx: DoctorHealthFlowContext): Promise<voi
999999
? await probeGatewayMemoryStatus({
10001000
cfg: ctx.cfg,
10011001
timeoutMs: ctx.options.nonInteractive === true ? 3000 : 10_000,
1002+
probe: ctx.options.deep === true,
10021003
})
10031004
: { checked: false, ready: false, skipped: healthOk };
10041005
}

0 commit comments

Comments
 (0)