diff --git a/server/src/nest/plugins/contributions/plugin-mcp-tools.service.ts b/server/src/nest/plugins/contributions/plugin-mcp-tools.service.ts index 338ca05ca..8db5a4828 100644 --- a/server/src/nest/plugins/contributions/plugin-mcp-tools.service.ts +++ b/server/src/nest/plugins/contributions/plugin-mcp-tools.service.ts @@ -62,9 +62,7 @@ export class PluginMcpToolsService implements OnApplicationBootstrap, OnModuleDe // needs PluginHooks, and PluginHooks injects PluginRuntimeService: owning it // there would be a cycle. Cleared on destroy, or a torn-down buildApp() leaves // a live source closing over a dead runtime and bleeds into the next suite. - // `async` with nothing to await: the call-graph gate keys on the `async` modifier - // and this frame reaches the DB through the source it installs. - async onApplicationBootstrap(): Promise { + onApplicationBootstrap(): void { setPluginMcpToolSource((ctx) => this.mcpTools(ctx)); } diff --git a/server/src/nest/plugins/plugin-runtime.service.ts b/server/src/nest/plugins/plugin-runtime.service.ts index 736929ada..97ff27a45 100644 --- a/server/src/nest/plugins/plugin-runtime.service.ts +++ b/server/src/nest/plugins/plugin-runtime.service.ts @@ -263,9 +263,11 @@ export class PluginRuntimeService implements OnApplicationBootstrap, OnModuleDes // scheduler is minute-granularity by contract, so 30s precision is plenty and // cheap. Unref'd so it never holds the process open. this.schedulerSweep = setInterval(() => { - // R1.5: a timer callback cannot await. Both helpers swallow their own errors - // (each body is wrapped in try/catch), so there is nothing to propagate. - void this.fireDueScheduled(); + // R1.5: a timer callback cannot await. Both helpers already swallow their own + // errors (each body is wrapped in try/catch); the .catch below is defense-in- + // depth against a future regression, same shape as the pruneErrorLog call in + // the onLog hook above. + void this.fireDueScheduled().catch(() => { /* fireDueScheduled already handles its own errors — this is a backstop */ }); void this.drainUserErasures(); }, 30_000); this.schedulerSweep.unref?.(); @@ -871,7 +873,7 @@ export class PluginRuntimeService implements OnApplicationBootstrap, OnModuleDes status = 'inactive', enabled = 0 WHERE id = ?`, ).run(DEV_LINK_SOURCE, id); - await this.watchLinked(id, sourceDir); + this.watchLinked(id, sourceDir); return { id, version: manifest.version, replaced, trekRangeBypassed }; } @@ -892,10 +894,8 @@ export class PluginRuntimeService implements OnApplicationBootstrap, OnModuleDes if (wasActive) await this.activate(id); } - /** Best-effort fs.watch on a linked plugin's built output that debounces -> reload. - * `async` with nothing to await: the call-graph gate keys on the `async` modifier and - * this frame reaches the DB through the debounced `reload` below. */ - private async watchLinked(id: string, sourceDir: string): Promise { + /** Best-effort fs.watch on a linked plugin's built output that debounces -> reload. */ + private watchLinked(id: string, sourceDir: string): void { this.stopWatch(id); const serverDir = path.join(sourceDir, 'server'); // the loader runs server/index.js try { diff --git a/server/tests/helpers/rpc-host-deps.ts b/server/tests/helpers/rpc-host-deps.ts index 5f63ba54e..f482ff364 100644 --- a/server/tests/helpers/rpc-host-deps.ts +++ b/server/tests/helpers/rpc-host-deps.ts @@ -43,7 +43,7 @@ export function makeDeps(): HostDeps { close: vi.fn(), } as unknown as HostDeps['data'], callPlugin: vi.fn(async (targetId: string, fn: string) => ({ calledTarget: targetId, calledFn: fn })), - emitPluginEvent: vi.fn(), + emitPluginEvent: vi.fn(async () => {}), }; }