fix: preserve batch failure state across EmbeddingService recreations

- Add transferBatchState() method to EmbeddingService to preserve failure
  tracking when the service is recreated during reindex operations
- Update manager.createEmbeddingService() to transfer state by default
- Skip state transfer when switching to fallback provider (fresh start)
- Add mock rateLimiter to send.test.ts to fix test failures
- Add error handling in audit-log.ts for ENOENT during test cleanup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ronitchidara 2026-01-28 17:41:49 +05:30
parent 9dd38535e4
commit 3034accd75
4 changed files with 43 additions and 9 deletions

View File

@ -45,6 +45,11 @@ vi.mock("../../config/sessions.js", async () => {
const makeContext = (): GatewayRequestContext =>
({
dedupe: new Map(),
rateLimiter: {
checkChannelMessage: () => ({ allowed: true }),
checkRequest: () => ({ allowed: true }),
recordAuthFailure: () => {},
},
}) as unknown as GatewayRequestContext;
describe("gateway send mirroring", () => {

View File

@ -122,6 +122,20 @@ export class EmbeddingService {
};
}
/**
* Transfer batch failure state from another EmbeddingService instance.
* Used when recreating the service to preserve failure tracking across reindexes.
*/
transferBatchState(from: EmbeddingService): void {
this.batchFailureCount = from.batchFailureCount;
this.batchFailureLastError = from.batchFailureLastError;
this.batchFailureLastProvider = from.batchFailureLastProvider;
// Also transfer disabled state if the source had batch disabled due to failures
if (!from.batch.enabled && from.batchFailureCount >= BATCH_FAILURE_LIMIT) {
this.batch.enabled = false;
}
}
/**
* Get cache entry count.
*/

View File

@ -1156,7 +1156,7 @@ export class MemoryIndexManager {
return /embedding|embeddings|batch/i.test(message);
}
private createEmbeddingService(): EmbeddingService {
private createEmbeddingService(preserveBatchState = true): EmbeddingService {
const batch = this.settings.remote?.batch;
const enabled = Boolean(
batch?.enabled &&
@ -1178,7 +1178,12 @@ export class MemoryIndexManager {
},
agentId: this.agentId,
};
return new EmbeddingService(this.db, config);
const service = new EmbeddingService(this.db, config);
// Preserve batch failure state across service recreations (e.g., during reindex)
if (preserveBatchState && this.embeddingService) {
service.transferBatchState(this.embeddingService);
}
return service;
}
private async activateFallbackProvider(reason: string): Promise<boolean> {
@ -1210,7 +1215,8 @@ export class MemoryIndexManager {
this.openAi = fallbackResult.openAi;
this.gemini = fallbackResult.gemini;
this.providerKey = this.computeProviderKey();
this.embeddingService = this.createEmbeddingService();
// Don't preserve batch state when switching providers - start fresh with new provider
this.embeddingService = this.createEmbeddingService(false);
log.warn(`memory embeddings: switched to fallback provider (${fallback})`, { reason });
return true;
}

View File

@ -110,14 +110,23 @@ function getArchivePath(basePath: string, date: Date): string {
async function appendAuditEntry(entry: AuditLogEntry, baseDir?: string): Promise<void> {
const { dir, currentPath } = resolveAuditPaths(baseDir);
// Ensure directory exists
await fs.mkdir(dir, { recursive: true });
try {
// Ensure directory exists
await fs.mkdir(dir, { recursive: true });
// Serialize entry
const line = JSON.stringify(entry) + "\n";
// Serialize entry
const line = JSON.stringify(entry) + "\n";
// Append to file (create if doesn't exist)
await fs.appendFile(currentPath, line, { encoding: "utf8", mode: 0o600 });
// Append to file (create if doesn't exist)
await fs.appendFile(currentPath, line, { encoding: "utf8", mode: 0o600 });
} catch (err) {
// Silently ignore errors if the directory was removed (e.g., during test cleanup)
// This prevents unhandled rejections when the audit log is disabled or the path is invalid
const code = (err as NodeJS.ErrnoException).code;
if (code !== "ENOENT" && code !== "ENOTDIR") {
throw err;
}
}
}
/**