From d4df747f9fbf39515ce1df4b413df46237394ccd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 20 Jan 2026 01:43:59 +0000 Subject: [PATCH] fix: harden doctor config cleanup --- src/cli/memory-cli.ts | 18 ++++++++++++------ src/commands/doctor-config-flow.ts | 17 +++++++++++++---- src/memory/manager.ts | 4 +++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index e890ce4e3..a10539ea7 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -85,8 +85,9 @@ async function scanSessionFiles(agentId: string): Promise { const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId); try { const entries = await fs.readdir(sessionsDir, { withFileTypes: true }); - const totalFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")) - .length; + const totalFiles = entries.filter( + (entry) => entry.isFile() && entry.name.endsWith(".jsonl"), + ).length; return { source: "sessions", totalFiles, issues }; } catch (err) { const code = (err as NodeJS.ErrnoException).code; @@ -246,7 +247,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { await manager.probeVectorAvailability(); } const status = manager.status(); - const sources = (status.sources?.length ? status.sources : ["memory"]) as MemorySourceName[]; + const sources = ( + status.sources?.length ? status.sources : ["memory"] + ) as MemorySourceName[]; const scan = await scanMemorySources({ workspaceDir: status.workspaceDir, agentId, @@ -305,8 +308,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { if (status.sourceCounts?.length) { lines.push(label("By source")); for (const entry of status.sourceCounts) { - const total = scan?.sources.find((scanEntry) => scanEntry.source === entry.source) - ?.totalFiles; + const total = scan?.sources.find( + (scanEntry) => scanEntry.source === entry.source, + )?.totalFiles; const counts = total === null ? `${entry.files}/? files ยท ${entry.chunks} chunks` @@ -373,7 +377,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { const batchState = status.batch.enabled ? "enabled" : "disabled"; const batchColor = status.batch.enabled ? theme.success : theme.warn; const batchSuffix = ` (failures ${status.batch.failures}/${status.batch.limit})`; - lines.push(`${label("Batch")} ${colorize(rich, batchColor, batchState)}${muted(batchSuffix)}`); + lines.push( + `${label("Batch")} ${colorize(rich, batchColor, batchState)}${muted(batchSuffix)}`, + ); if (status.batch.lastError) { lines.push(`${label("Batch error")} ${warn(status.batch.lastError)}`); } diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index 8c2ca9b40..27f7d6bc1 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -18,9 +18,13 @@ function isRecord(value: unknown): value is Record { type UnrecognizedKeysIssue = ZodIssue & { code: "unrecognized_keys"; - keys: string[]; + keys: PropertyKey[]; }; +function normalizeIssuePath(path: PropertyKey[]): Array { + return path.filter((part): part is string | number => typeof part !== "symbol"); +} + function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssue { return issue.code === "unrecognized_keys"; } @@ -55,7 +59,10 @@ function resolvePathTarget(root: unknown, path: Array): unknown return current; } -function stripUnknownConfigKeys(config: ClawdbotConfig): { config: ClawdbotConfig; removed: string[] } { +function stripUnknownConfigKeys(config: ClawdbotConfig): { + config: ClawdbotConfig; + removed: string[]; +} { const parsed = ClawdbotSchema.safeParse(config); if (parsed.success) { return { config, removed: [] }; @@ -65,13 +72,15 @@ function stripUnknownConfigKeys(config: ClawdbotConfig): { config: ClawdbotConfi const removed: string[] = []; for (const issue of parsed.error.issues) { if (!isUnrecognizedKeysIssue(issue)) continue; - const target = resolvePathTarget(next, issue.path); + const path = normalizeIssuePath(issue.path); + const target = resolvePathTarget(next, path); if (!target || typeof target !== "object" || Array.isArray(target)) continue; const record = target as Record; for (const key of issue.keys) { + if (typeof key !== "string") continue; if (!(key in record)) continue; delete record[key]; - removed.push(formatPath([...issue.path, key])); + removed.push(formatPath([...path, key])); } } diff --git a/src/memory/manager.ts b/src/memory/manager.ts index 771b5eeab..0e1a173bc 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -1795,7 +1795,9 @@ export class MemoryIndexManager { if (!this.batch.enabled) { return { disabled: true, count: this.batchFailureCount }; } - const increment = params.forceDisable ? BATCH_FAILURE_LIMIT : Math.max(1, params.attempts ?? 1); + const increment = params.forceDisable + ? BATCH_FAILURE_LIMIT + : Math.max(1, params.attempts ?? 1); this.batchFailureCount += increment; this.batchFailureLastError = params.message; this.batchFailureLastProvider = params.provider;