fix: harden doctor config cleanup

This commit is contained in:
Peter Steinberger
2026-01-20 01:43:59 +00:00
parent 8e33bd8610
commit d4df747f9f
3 changed files with 28 additions and 11 deletions

View File

@@ -85,8 +85,9 @@ async function scanSessionFiles(agentId: string): Promise<SourceScan> {
const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId); const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId);
try { try {
const entries = await fs.readdir(sessionsDir, { withFileTypes: true }); const entries = await fs.readdir(sessionsDir, { withFileTypes: true });
const totalFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")) const totalFiles = entries.filter(
.length; (entry) => entry.isFile() && entry.name.endsWith(".jsonl"),
).length;
return { source: "sessions", totalFiles, issues }; return { source: "sessions", totalFiles, issues };
} catch (err) { } catch (err) {
const code = (err as NodeJS.ErrnoException).code; const code = (err as NodeJS.ErrnoException).code;
@@ -246,7 +247,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
await manager.probeVectorAvailability(); await manager.probeVectorAvailability();
} }
const status = manager.status(); 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({ const scan = await scanMemorySources({
workspaceDir: status.workspaceDir, workspaceDir: status.workspaceDir,
agentId, agentId,
@@ -305,8 +308,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
if (status.sourceCounts?.length) { if (status.sourceCounts?.length) {
lines.push(label("By source")); lines.push(label("By source"));
for (const entry of status.sourceCounts) { for (const entry of status.sourceCounts) {
const total = scan?.sources.find((scanEntry) => scanEntry.source === entry.source) const total = scan?.sources.find(
?.totalFiles; (scanEntry) => scanEntry.source === entry.source,
)?.totalFiles;
const counts = const counts =
total === null total === null
? `${entry.files}/? files · ${entry.chunks} chunks` ? `${entry.files}/? files · ${entry.chunks} chunks`
@@ -373,7 +377,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
const batchState = status.batch.enabled ? "enabled" : "disabled"; const batchState = status.batch.enabled ? "enabled" : "disabled";
const batchColor = status.batch.enabled ? theme.success : theme.warn; const batchColor = status.batch.enabled ? theme.success : theme.warn;
const batchSuffix = ` (failures ${status.batch.failures}/${status.batch.limit})`; 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) { if (status.batch.lastError) {
lines.push(`${label("Batch error")} ${warn(status.batch.lastError)}`); lines.push(`${label("Batch error")} ${warn(status.batch.lastError)}`);
} }

View File

@@ -18,9 +18,13 @@ function isRecord(value: unknown): value is Record<string, unknown> {
type UnrecognizedKeysIssue = ZodIssue & { type UnrecognizedKeysIssue = ZodIssue & {
code: "unrecognized_keys"; code: "unrecognized_keys";
keys: string[]; keys: PropertyKey[];
}; };
function normalizeIssuePath(path: PropertyKey[]): Array<string | number> {
return path.filter((part): part is string | number => typeof part !== "symbol");
}
function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssue { function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssue {
return issue.code === "unrecognized_keys"; return issue.code === "unrecognized_keys";
} }
@@ -55,7 +59,10 @@ function resolvePathTarget(root: unknown, path: Array<string | number>): unknown
return current; return current;
} }
function stripUnknownConfigKeys(config: ClawdbotConfig): { config: ClawdbotConfig; removed: string[] } { function stripUnknownConfigKeys(config: ClawdbotConfig): {
config: ClawdbotConfig;
removed: string[];
} {
const parsed = ClawdbotSchema.safeParse(config); const parsed = ClawdbotSchema.safeParse(config);
if (parsed.success) { if (parsed.success) {
return { config, removed: [] }; return { config, removed: [] };
@@ -65,13 +72,15 @@ function stripUnknownConfigKeys(config: ClawdbotConfig): { config: ClawdbotConfi
const removed: string[] = []; const removed: string[] = [];
for (const issue of parsed.error.issues) { for (const issue of parsed.error.issues) {
if (!isUnrecognizedKeysIssue(issue)) continue; 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; if (!target || typeof target !== "object" || Array.isArray(target)) continue;
const record = target as Record<string, unknown>; const record = target as Record<string, unknown>;
for (const key of issue.keys) { for (const key of issue.keys) {
if (typeof key !== "string") continue;
if (!(key in record)) continue; if (!(key in record)) continue;
delete record[key]; delete record[key];
removed.push(formatPath([...issue.path, key])); removed.push(formatPath([...path, key]));
} }
} }

View File

@@ -1795,7 +1795,9 @@ export class MemoryIndexManager {
if (!this.batch.enabled) { if (!this.batch.enabled) {
return { disabled: true, count: this.batchFailureCount }; 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.batchFailureCount += increment;
this.batchFailureLastError = params.message; this.batchFailureLastError = params.message;
this.batchFailureLastProvider = params.provider; this.batchFailureLastProvider = params.provider;