From 3bea082f20ddd16984302f116e5e598f688938bc Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sun, 19 Apr 2026 08:33:29 +0200 Subject: [PATCH] auto-dispatch: silence expected registry fallback on non-auto commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sf headless query and sf headless status call resolveDispatch() without going through auto-mode startup, so the rule-registry singleton is never initialized. The previous code caught getRegistry()'s init error and logged a warning on every call — noise on the normal path: [sf:dispatch] WARN: registry dispatch failed, falling back to inline rules: RuleRegistry not initialized — call initRegistry() or setRegistry() first. Now: hasRegistry() probe first. When unset, skip straight to the inline rule loop without warning (it's the intended behavior outside auto). When the registry IS set and evaluateDispatch() genuinely throws, log the warning so real bugs still surface. Adds hasRegistry() as a public helper for any other hot-path caller that wants to branch on init without try/catch overhead. Verified end-to-end: sf headless query and sf headless status in dr-repo now run clean, no false warning. All 25 rule-registry tests pass. Co-Authored-By: Claude Sonnet 4.6 --- src/resources/extensions/sf/auto-dispatch.ts | 21 ++++++++++++-------- src/resources/extensions/sf/rule-registry.ts | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/resources/extensions/sf/auto-dispatch.ts b/src/resources/extensions/sf/auto-dispatch.ts index 066137234..d15c4dfa5 100644 --- a/src/resources/extensions/sf/auto-dispatch.ts +++ b/src/resources/extensions/sf/auto-dispatch.ts @@ -900,7 +900,7 @@ export const DISPATCH_RULES: DispatchRule[] = [ }, ]; -import { getRegistry } from "./rule-registry.js"; +import { getRegistry, hasRegistry } from "./rule-registry.js"; // ─── Resolver ───────────────────────────────────────────────────────────── @@ -915,13 +915,18 @@ import { getRegistry } from "./rule-registry.js"; export async function resolveDispatch( ctx: DispatchContext, ): Promise { - // Delegate to registry when available - try { - const registry = getRegistry(); - return await registry.evaluateDispatch(ctx); - } catch (err) { - // Registry not initialized — fall back to inline loop - logWarning("dispatch", `registry dispatch failed, falling back to inline rules: ${err instanceof Error ? err.message : String(err)}`); + // Delegate to registry when available. Callers that run outside auto-mode + // (e.g. `sf headless query`, `sf headless status`) never initialize the + // registry — falling through to inline rules is the intended behavior, + // not an error, so we silent-probe instead of warning on every call. + if (hasRegistry()) { + try { + return await getRegistry().evaluateDispatch(ctx); + } catch (err) { + // Genuine registry evaluation failure (rule threw, etc.) — log so we + // surface real bugs, then fall back. + logWarning("dispatch", `registry dispatch failed, falling back to inline rules: ${err instanceof Error ? err.message : String(err)}`); + } } for (const rule of DISPATCH_RULES) { diff --git a/src/resources/extensions/sf/rule-registry.ts b/src/resources/extensions/sf/rule-registry.ts index 74af0ad3d..3068c9393 100644 --- a/src/resources/extensions/sf/rule-registry.ts +++ b/src/resources/extensions/sf/rule-registry.ts @@ -561,6 +561,12 @@ export class RuleRegistry { let _registry: RuleRegistry | null = null; +/** True when the singleton registry has been initialized. Lets callers branch + * without paying for try/catch on a hot path. */ +export function hasRegistry(): boolean { + return _registry !== null; +} + /** Get the singleton registry. Throws if not initialized. */ export function getRegistry(): RuleRegistry { if (!_registry) {