auto-dispatch: silence expected registry fallback on non-auto commands

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 <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-04-19 08:33:29 +02:00
parent 56130c2e39
commit 3bea082f20
2 changed files with 19 additions and 8 deletions

View file

@ -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<DispatchAction> {
// 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) {

View file

@ -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) {