angular/angular-cli · error · InvalidRuleResultException

Invalid rule result: ${_getTypeOfResult(value)}.

Error message

Invalid rule result: ${_getTypeOfResult(value)}.

What it means

callRule (async path) requires every Rule to return a Tree, an Observable<Tree>, or void/undefined for in-place mutation. When the rule's resolved result is a truthy value that is not a Tree (no TreeSymbol), InvalidRuleResultException is thrown. It enforces the Rule return contract.

Source

Thrown at packages/angular_devkit/schematics/src/rules/call.ts:97

  while (typeof result === 'function') {
    // This is considered a Rule, chain the rule and return its output.
    result = await result(tree, context);
  }

  if (typeof result === 'undefined') {
    return tree;
  }

  if (isObservable(result)) {
    result = await lastValueFrom(result.pipe(defaultIfEmpty(tree)));
  }

  if (result && TreeSymbol in result) {
    return result;
  }

  throw new InvalidRuleResultException(result);
}

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Return the input tree from the rule (or nothing) so the contract Tree|Observable<Tree>|void is met.
  2. If you need async work, return an Observable<Tree> (e.g. from(callRule(...))) not a Promise of another value.
  3. Remove accidental returns: wrap the value-producing call in a statement without returning it.

Example fix

// before
return (tree: Tree) => tree.visit(f => log(f.path)); // visit returns void-ish, not a Tree
// after
return (tree: Tree) => { tree.visit(f => log(f.path)); return tree; };
Defensive patterns

Strategy: type-guard

Validate before calling

function ensureRuleResult(v: unknown): Tree | Observable<Tree> | void {
  if (v === undefined) return v;
  if (v && typeof v === 'object' && TreeSymbol in (v as object)) return v as Tree;
  throw new Error('Rule must return Tree | Observable<Tree> | void');
}

Type guard

function isRuleResult(v: unknown): v is Tree | Observable<Tree> | undefined {
  return v === undefined || (typeof v === 'object' && v !== null && (TreeSymbol in (v as object) || typeof (v as any).subscribe === 'function'));
}

Try / catch

try {
  await firstValueFrom(callRule(myRule, tree, context));
} catch (err) {
  if (err instanceof InvalidRuleResultException) {
    console.error('Rule returned a non-Tree value:', err.message);
  }
}

Prevention

When it happens

Trigger: A Rule returning something other than Tree|Observable<Tree>|void — e.g. returning a string, a FileEntry, a Promise of a non-tree, or an object literal.

Common situations: Custom rules that accidentally `return tree.visit(...)` (returns void/false-y data), `return someOtherCall()` leaking a non-tree, or TypeScript types bypassed with `as any`.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/b5b343d09f225b96. Report an issue: GitHub.