diff --git a/src/services/startup.ts b/src/services/startup.ts index 20fd1de..d8782de 100644 --- a/src/services/startup.ts +++ b/src/services/startup.ts @@ -267,11 +267,21 @@ async function resumeProject( return; } - // Index is complete — start watcher and do incremental catch-up + // Index is complete — start watcher and do incremental catch-up. + // Watcher startup failure must not skip the catch-up below: a project + // without a live watcher is degraded, but a project with a stale index + // is broken for search. if (!isWatching(resolvedPath)) { - const started = await startWatching(resolvedPath); - if (started) { - logger.info("Auto-resume: started file watcher", { projectPath: resolvedPath }); + try { + const started = await startWatching(resolvedPath); + if (started) { + logger.info("Auto-resume: started file watcher", { projectPath: resolvedPath }); + } + } catch (err) { + logger.warn("Auto-resume: failed to start watcher, continuing with incremental catch-up", { + projectPath: resolvedPath, + error: err instanceof Error ? err.message : String(err), + }); } } diff --git a/tests/unit/startup.test.ts b/tests/unit/startup.test.ts index 0215baf..ff009b1 100644 --- a/tests/unit/startup.test.ts +++ b/tests/unit/startup.test.ts @@ -554,6 +554,21 @@ describe("autoResumeIndexedProjects (multi-project modes)", () => { expect(mockUpdateProjectIndex).not.toHaveBeenCalled(); }); + it("still runs the incremental catch-up when watcher startup throws", async () => { + process.env.SOCRATICODE_AUTO_RESUME_PROJECTS = dirA; + mockStartWatching.mockRejectedValue(new Error("inotify limit reached")); + + await autoResumeIndexedProjects(); + + // A project without a live watcher is degraded; a project with a stale + // index is broken for search. The catch-up must survive watcher failure. + expect(logger.warn).toHaveBeenCalledWith( + "Auto-resume: failed to start watcher, continuing with incremental catch-up", + expect.objectContaining({ projectPath: dirA, error: "inotify limit reached" }), + ); + expect(mockUpdateProjectIndex).toHaveBeenCalledWith(dirA); + }); + it("unrecognized SOCRATICODE_AUTO_RESUME value warns and falls back to default cwd behavior", async () => { process.env.SOCRATICODE_AUTO_RESUME = "yes"; mockListCollections.mockResolvedValue([collA]);