Skip to content

feat(sdk): hook ordering with HookPhase bands and before/after, an observer chain, a dev hook trace and appliesTo: 'all-apps' - #1005

Merged
alexmercerpo merged 10 commits into
feat/m08-live-surfacefrom
feat/m07-hook-ordering
Nov 2, 2026
Merged

alexmercerpo merged 10 commits into
feat/m08-live-surfacefrom
feat/m07-hook-ordering

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1004

What and why

Proposal items 1.3 (its additive part), 3.4 and 3.3: hooks that order themselves by meaning instead of magic numbers,
a dev trace of the hooks a call ran, and one app's hook that runs for every app.

The reported problems:

  • Hook order is set by numbers that mean nothing. A plugin that must run before another picks a number on the far
    side of the other's priority (10001 against 10000), and nothing tells a reader why. fix(sdk): document hook priority order, sort the hook registry in run order and print the resolved order in dev mode #985 documented the
    direction (lowest first) and added a dev-mode print of the resolved order; there was still no way to say "before
    the setup gate", and no shared vocabulary for where a guard or an answer shaper belongs.
  • Nothing shows which hooks touched a call. The order print shows what is registered, not what ran for one call
    or how long each hook took, so an order bug (a refused call that was still timed, a refusal from the wrong gate)
    was found only by reading code.
  • A request had one hook-observer slot. @frontmcp/observability claimed it, and any other observer installed
    on the same request was dropped without a word, so nothing else could watch hooks next to it.
  • An app's plugin hook could not guard every app. A setup gate declared next to the app that owns it saw only
    that app's tools, so it had to move to the server's plugins. appliesTo: 'uncovered-apps' fills gaps for gates an
    entry asks for, but steps aside for any app with its own instance.

What this PR changes:

  • HookPhase: named priorities in run order, resolve (-100), guard (100), observe (300) and shape (500),
    spaced so a hook can sit between two (HookPhase.guard + 10). The default priority stays 0, between resolve and
    guard, and no built-in priority changes: approval sits at guard, feature flags at 50, CodeCall's hidden-tool
    refusal at 10, the cache plugin at 1000 (after every band), and observability opens its spans at -1000 and closes
    them at 1000, outside every band.
  • before / after on every hook decorator: lists of hook targets, each a plugin's name ('setup-gate') or
    the name of the class that declares the hook (a plugin, provider or entry class), optionally followed by .method
    for one hook. Within one flow, stage and type, the hooks are sorted topologically on these constraints, falling
    back to priority and then registration order. The hook that declares a constraint is the one that moves: with
    before it takes the place of the earliest hook it names, with after it waits for the hooks it names, and the
    hooks it names keep their place among the others (so an observe hook with before: ['tenant-resolver'] never
    pulls a guard hook ahead of the resolver). For Around hooks, before means "wraps". A constraint never relates
    a hook to itself or to another instance of the same hook, and never names the flow's own steps.
    • A cycle is a startup error, HookOrderCycleError (HOOK_ORDER_CYCLE), whose message and hooks name the hooks
      in the cycle, in order. The check runs when each scope (and each agent scope) starts, over the hooks one call can
      sort together: all of the scope's plugin, provider and app hooks (a call naming an unknown tool runs every
      app's, so two apps' hooks can form a cycle), with those an agent inherits through execution.inheritPlugins,
      and each entry class's hooks with them, one class at a time (two entry classes never share a call). A run
      still refuses a cycle it meets, such as one through an entry registered after startup.
    • A target that matches no hook of the stage logs one warning per hook at startup, naming every such target.
    • before and after must be arrays of non-empty strings with no surrounding spaces, and appliesTo one of its
      values: anything else throws InvalidDecoratorMetadataError where the hook is declared (Zod-checked). Before, an
      unknown appliesTo was silently treated as 'own-app', and before / after were not hook options.
    • The dev-mode order print (fix(sdk): document hook priority order, sort the hook registry in run order and print the resolved order in dev mode #985) shows each step's constraints next to its priority:
      will FeatureGatePlugin.refuseDisabled (100, before setup-gate).
  • An observer chain. The request's single observer slot becomes a list under the same
    Symbol.for('frontmcp:hook-observer') key. addHookObserver(context, observer) adds one; every installed
    observer wraps the hook, the first added outermost. Whatever an observer does, the hook runs exactly once and
    settles as it would unobserved: an observer that throws (before or after running the hook), never runs it or runs it
    twice changes nothing, and its throw is logged once per observer as a warning. A plain function left under the key
    (an older @frontmcp/observability) still counts as one observer. @frontmcp/observability now registers through
    the chain, still once per request.
  • The dev hook trace. In dev mode (NODE_ENV=development), or with the new @FrontMcp({ devtools: { hookTrace: true } }), every tool, resource and prompt answer carries _meta['frontmcp/trace'] (HOOK_TRACE_META_KEY): the
    hooks that ran for the call, in the order they started, each as { owner, method, stage, type, ms, outcome }.
    It is recorded by an observer appended to the chain for the run, so it times the hook itself. It is added once the
    answer is final, after the answer budget and the cost meter, so neither counts it. It lists the hooks
    of plugins, providers, apps and entry classes (not the flow's own steps), and never a hook's arguments, the
    call's input or its answer. devtools.hookTrace: false turns it off in dev mode; a non-boolean stops the server
    at startup. Outside dev mode, without hookTrace: true, answers never carry it.
  • appliesTo: 'all-apps': one hook of an app's plugin runs for every app's entries in its scope, and for the
    tools the server declares, as a server-level plugin's hook would, even for an app with its own instance of the same
    hook (then both run). It differs from 'uncovered-apps', which steps aside for an app that has its own instance,
    and from @Plugin({ scope: 'server' }), which widens every hook of the plugin and cannot be installed on a
    standalone app. Both reach only the apps that share a scope with the installing app. This is a deliberate
    widening of trust: an 'all-apps' hook reads, and can change, the arguments and answers of every app's calls in
    that scope, remote apps' included, as a server-level plugin's hooks do, and the catalog says to grant it only to a
    plugin one would install on the server. Remote and ESM-loaded apps install no plugins, so they cannot declare one. The startup checks of
    plugin-enforced metadata (approval, featureFlag, ...) count an 'all-apps' hook as covering every app, in the
    full check and in the static check that edge entry points run.

Choices that differ from the plan, and why:

  • Trace entries add method to { owner, stage, type, ms, outcome }, so two hooks of one plugin on a stage can
    be told apart. owner is the plugin's name (else the declaring class's name), the same name before / after
    use.
  • Band values were chosen so every built-in plugin keeps its priority (the brief's "0 stays 0"); the bands are
    documented with where each built-in sits.
  • The trace option is devtools.hookTrace, and an explicit false wins over dev mode.

User-facing change

  • What changed: new HookPhase bands; new before / after hook options with a startup cycle error and an
    unknown-target warning; Zod validation of appliesTo, before and after; a hook-observer chain
    (addHookObserver); a dev-only _meta['frontmcp/trace'] on tool, resource and prompt answers and the
    devtools.hookTrace option; per-hook appliesTo: 'all-apps'.
  • Before → after:
    • Hook order: priorities only → before / after first, then priority, then registration order. Run order is
      unchanged for hooks that declare neither.
    • @ToolHook.Will('execute', { appliesTo: 'everywhere' }): treated as 'own-app' → throws
      InvalidDecoratorMetadataError: @Will('execute') invalid metadata for "appliesTo": must be 'own-app', 'uncovered-apps' or 'all-apps'. Any appliesTo other than the three values, a typo or a value of a later
      release, now stops the module that declares the hook from loading.
    • before / after that are not arrays of non-empty strings, or a target with surrounding spaces
      ({ before: [' setup-gate '] }): ignored (not hook options) → throws InvalidDecoratorMetadataError naming
      "before" or "after": a hook target is a plugin name or a class name, optionally followed by .method, with no surrounding spaces.
    • Hooks whose constraints form a cycle: not possible → the server does not start, HookOrderCycleError
      (HOOK_ORDER_CYCLE): The before/after options of the will hooks on 'execute' of 'tools:call-tool' form a cycle: setup-gate.requireSetup → feature-gate.refuseDisabled → setup-gate.requireSetup. Remove one of these constraints.
    • A target no hook matches: nothing → one startup warning: Hook feature-gate.refuseDisabled (will 'execute' of 'tools:call-tool') names 'setup-gate' in before, but no hook on that stage matches it, so it changes nothing. A target is a plugin name or the class that declares the hook, optionally followed by .method.
    • Dev-mode answers (NODE_ENV=development) of tools/call, resources/read and prompts/get: no trace → a
      _meta['frontmcp/trace'] array. Production and test answers: unchanged.
    • A second hook observer on a request: silently dropped → runs next to the first. A throwing observer: broke the
      hook → logged once and ignored.
    • The dev-mode hook order print: (priority) → (priority, before x, after y) for a step with constraints.
  • frontmcp.dev pages affected (if known): the plugin hooks reference (https://frontmcp.dev/reference/sdk/plugin):
    "Hook order" (HookPhase, before / after, the print), hook options (before, after, appliesTo: 'all-apps'), a new "Hook trace" section and "Hook observers"; the @FrontMcp options reference (devtools); the
    errors reference (HookOrderCycleError); multi-app composition (appliesTo: 'all-apps' and shared tools).
  • Skills catalog entries updated:
    • frontmcp-development/references/create-plugin-hooks.md: the "Hook Order" section gains "Named Bands:
      HookPhase" (with the built-in plugins' places), "Ordering Against Another Hook: before and after" (targets,
      scope, precedence, errors, validation), the constraints in "Seeing the Resolved Order", "Tracing a Call in Dev
      Mode" and "Watching Every Hook: Hook Observers"; "HookOptions" documents before, after and the three
      appliesTo values against @Plugin({ scope: 'server' }); two troubleshooting rows (the cycle error, the
      unmatched-target warning).
    • frontmcp-development/references/create-plugin.md: per-hook appliesTo next to "Hook scope", ordering against
      another plugin, and a common-patterns row.
    • frontmcp-extensibility/SKILL.md: routing rows for ordering hooks and for one app's hook running for every app.
    • frontmcp-setup/references/multi-app-composition.md: 'all-apps' hooks reach shared tools.

Tests

  • Red first: the new unit specs failed (no HookPhase, no hook-order / hook-observers modules); the static
    startup case and the observability coexistence case were checked red by restoring the old code for the run.
  • Unit (libs/sdk):
    • hooks/__tests__/hook-order.spec.ts: HookPhase values and slotting between bands; before and after by
      plugin name, class name and Name.method, overriding priority; the hook that declares before or after moves,
      while the hooks it names keep their place past the bands between them (a chain of before included); unrelated
      hooks keep priority, then registration order; a hook never relates to itself or another instance of it; the flow's own step is never a target; an
      unmatched target changes nothing; a cycle throws HookOrderCycleError naming the hooks; the startup check
      relates only hooks of one flow, stage and type, warns once per hook about every unmatched target and says
      nothing when all match; the decorator refuses an invalid appliesTo, before or after and keeps valid ones.
    • hooks/__tests__/hook-observers.spec.ts: two observers both run, the first outermost; one that throws before or
      after running the hook doesn't break it and is reported; an observer cannot swallow the hook's failure, nor
      replace it with its own throw (only that throw is reported); a hook runs once, in the request's async context,
      when an observer calls run after an await or after it settled, never calls it, or calls it twice; the chain keeps install order and adds an
      observer once, and reads a single legacy function; the trace observer records owner, method, stage, type, ms and
      outcome in start order; withHookTrace adds to _meta without changing the answer it was given.
    • hooks/__tests__/flow-hooks.ordering.spec.ts (real servers): constraints order three plugins against their
      priorities and plugin order; a cycle refuses to start the server, two apps' own-app hooks in a cycle too (a
      call naming an unknown tool runs both), while two entry classes whose hooks name each other start; an agent
      with execution.inheritPlugins matches its hooks' targets against the hooks it inherits, and a cycle with one
      refuses to start; an unmatched target warns once at startup;
      the trace on tool, resource and prompt answers in dev mode, absent in production and test, on with
      devtools.hookTrace: true and off with false, and a non-boolean refused; 'all-apps' runs one app's hook for
      every app's tools while its own-app hooks stay on its app.
    • flows/__tests__/flow-stage-hook-lists.spec.ts: the print shows a step's before and after.
    • front-mcp/__tests__/static-startup.check.spec.ts: an approval tool on one app, or a server-level one, is covered
      by an 'all-apps' gate on another app, in the full and the static checks.
  • Unit (libs/observability), server-telemetry.spec.ts: with another observer added first on the request, the
    request log still records the hooks that ran and the other observer sees them too.
  • E2E:
    • apps/e2e/demo-e2e-hooks/e2e/hook-ordering.e2e.spec.ts (server src/main.hook-ordering.ts): before runs the
      tenant resolver ahead of the quota gate despite its higher priority; an 'all-apps' hook of the first app runs
      for the second app's tool; a production server's answer has no trace; a dev server's answer lists the three hooks
      in run order.
    • apps/e2e/demo-e2e-dts-consumer: hook-ordering-must-compile.ts (HookPhase, before / after,
      appliesTo: 'all-apps', addHookObserver, HookObserver, HookTraceEntry, HOOK_TRACE_META_KEY,
      devtools.hookTrace) and hook-ordering-must-fail.ts (an invalid appliesTo, a string before, a class in
      after, a write to HookPhase), each case checked to fail once its @ts-expect-error is removed.
  • Ran:
    • jest libs/sdk (589 suites, 7657 tests), libs/observability (499), plugin-approval (427),
      plugin-feature-flags (243), plugin-codecall (1211), plugin-cache (202), plugin-remember (515),
      nx test skills (249): all green.
    • nx test demo-e2e-hooks (27, the fix(sdk): document hook priority order, sort the hook registry in run order and print the resolved order in dev mode #985 hook-order e2e included), nx test demo-e2e-dts-consumer (19),
      nx test demo-e2e-approval (6), demo-e2e-standalone (53, the observability span and request-log specs
      included, with @frontmcp/observability built): green.
    • nx run-many -t lint -p sdk observability skills: no errors, no warnings on changed lines;
      tsc --noEmit -p libs/sdk/tsconfig.lib.json and libs/sdk/tsconfig.spec.json: clean;
      node scripts/fix-unused-imports.mjs snap/m05 (no change); prettier on every changed file.

Summary by CodeRabbit

  • New Features
    • Control hook execution order with named before and after constraints and priority bands.
    • Apply hooks across all apps with the all-apps option.
    • View hook order, duration, and outcome in development trace metadata for tool, resource, and prompt responses.
    • Register multiple hook observers; observer errors do not prevent hooks from running.
  • Bug Fixes
    • Invalid hook options are rejected. Circular ordering constraints are reported during startup, and unmatched targets generate warnings.
  • Documentation
    • Added guidance on hook ordering, scope, tracing, and troubleshooting.

Bundle size

Hook ordering and the observer chain run in the flow runner for every flow, so the browser bundles in demo-e2e-bundle-size grow, as measured on CI against #1004:

  • create() + one tool: 1,310,872 → 1,317,511 bytes minified (+6,639; +2,747 gzipped). Budget 1,315,000 → 1,321,639.
  • create() + WebMcpPlugin + one tool: 1,318,185 → 1,324,700 bytes minified (+6,515; +2,728 gzipped). Budget 1,322,000 → 1,328,515.

Each budget moves up by exactly its measured growth (529cbb1). The Vite initial chunk (939,241 of 960,000) stays within its budget.

…ook observers, add a dev hook trace and per-hook appliesTo: 'all-apps'
…er the hooks one call runs

- A hook's before took effect by holding back the hooks it named, so they ran after
  every unrelated hook of a later band: an observe hook with before: ['tenant-resolver']
  ran a guard ahead of the tenant resolver. The hook that declares before now takes the
  place of the earliest hook it names; the hooks it names keep their place.
- The startup check related every hook of a scope at once, entry-class hooks included,
  and an agent's own hooks without those it inherits. It now checks the sets one call can
  sort together: a scope's flow hooks (an unknown entry runs every app's), with an agent's
  inherited hooks, and each entry class's hooks on their own. Two entry classes naming
  each other no longer stop the server; an agent hook ordered against an inherited one is
  no longer reported as unmatched, and a cycle with an inherited hook stops startup.
- Document that an 'all-apps' hook reads and can change every app's calls, and that the
  trace is added after the answer budget and the cost meter.
…/m07-hook-ordering

# Conflicts:
#	apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
#	apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 15:00
@frontegg-david
frontegg-david added this pull request to stack #997 November 2, 2026 15:00
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 7907553 and 529cbb1.

Files selected for processing: 2
  • apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts
  • apps/e2e/demo-e2e-hooks/src/apps/hook-ordering/tools/ran-hooks.tool.ts

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Walkthrough

The SDK adds hook ordering constraints, startup validation, and all-apps hook coverage. It also adds request-scoped hook observers and optional trace metadata on tool, resource, and prompt answers. Tests, E2E examples, and developer references cover these changes.

Changes: Hook Ordering and Tracing

Layer / File(s) Summary
Hook and devtools contracts
libs/sdk/src/common/metadata/hook.metadata.ts, libs/sdk/src/common/decorators/hook.decorator.ts, libs/sdk/src/common/types/options/devtools/*, libs/sdk/src/common/metadata/front-mcp.metadata.ts, libs/sdk/src/common/interfaces/flow.interface.ts, libs/sdk/src/common/tokens/front-mcp.tokens.ts, libs/sdk/src/errors/registry.errors.ts, libs/sdk/src/errors/index.ts, libs/sdk/src/plugin/plugin.registry.ts, libs/sdk/src/index.ts
Hook metadata adds priority bands, before and after targets, all-apps, and option validation. Devtools metadata adds the hookTrace setting and its schema.
Ordering constraints and app coverage
libs/sdk/src/hooks/hook-order.ts, libs/sdk/src/hooks/hook.registry.ts, libs/sdk/src/flows/flow.stages.ts, libs/sdk/src/scope/scope.instance.ts, libs/sdk/src/agent/agent.scope.ts, libs/sdk/src/front-mcp/static-startup.check.ts, libs/sdk/src/plugin/enforced-metadata.check.ts, libs/sdk/src/hooks/__tests__/hook-order.spec.ts, libs/sdk/src/hooks/__tests__/flow-hooks.ordering.spec.ts, libs/sdk/src/front-mcp/__tests__/static-startup.check.spec.ts, libs/sdk/src/flows/__tests__/flow-stage-hook-lists.spec.ts
Hook stages apply ordering constraints after priority and registration order. Scope and agent initialization check hook order and report unmatched targets or cycles. App- and agent-owned hooks with all-apps coverage can apply to other apps’ entries.
Observer execution and answer tracing
libs/sdk/src/hooks/hook-observers.ts, libs/sdk/src/flows/flow.instance.ts, libs/sdk/src/tool/flows/call-tool.flow.ts, libs/sdk/src/resource/flows/read-resource.flow.ts, libs/sdk/src/prompt/flows/get-prompt.flow.ts, libs/observability/src/plugin/observability.hooks.ts, libs/sdk/src/hooks/__tests__/hook-observers.spec.ts, libs/observability/src/__tests__/server-telemetry.spec.ts
The SDK supports request-scoped observer chains and records hook owner, method, stage, type, duration, and outcome. Supported flows can attach trace metadata to answers when tracing is enabled. The observability plugin registers through the shared observer API.
Consumer and E2E validation
apps/e2e/demo-e2e-dts-consumer/fixture/hook-ordering-*, apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json, apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts, apps/e2e/demo-e2e-hooks/src/apps/hook-ordering/*, apps/e2e/demo-e2e-hooks/src/main.hook-ordering.ts, apps/e2e/demo-e2e-hooks/e2e/hook-ordering.e2e.spec.ts, apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts
Compile-time fixtures check accepted and rejected hook options. E2E examples test hook order, cross-app hooks, and trace metadata in development and production modes. Bundle-size budgets increase by 6,639 bytes and 6,515 bytes for the two entries.
Hook behavior documentation
libs/skills/catalog/frontmcp-development/references/create-plugin-hooks.md, libs/skills/catalog/frontmcp-development/references/create-plugin.md, libs/skills/catalog/frontmcp-extensibility/SKILL.md, libs/skills/catalog/frontmcp-setup/references/multi-app-composition.md
Developer references describe ordering constraints, all-apps scope, trace metadata, observer behavior, and troubleshooting cases.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FlowInstance
  participant RequestContext
  participant ObserverUtilities
  participant Hook
  FlowInstance->>RequestContext: Read registered observers
  FlowInstance->>ObserverUtilities: Run observers with hook metadata
  ObserverUtilities->>Hook: Invoke hook through once-only continuation
  Hook-->>ObserverUtilities: Return outcome
  ObserverUtilities-->>FlowInstance: Complete observed hook
  FlowInstance->>FlowInstance: Add trace metadata to answer when enabled
Loading

Possibly related issues

  • agentfront/frontmcp#950 — The PR changes hook targeting and ordering, but the issue’s SDK bundle-splitting requirement remains outside this PR.

Suggested reviewers: alexmercerpo

Merge Risk: Minimal · up to 529cb

The reviewed changes do not show a concrete production or workflow-blocking failure.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly summarizes the main changes: hook ordering, observer chaining, development tracing, and all-apps hook scope. It is long, but remains specific and relevant.
Description check Passed The description follows the required sections and explains the changes, user-facing behavior, affected documentation, skills catalog updates, and tests in detail.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-02T16:16:29.699Z
Commit: f9c54545

@frontegg-david
frontegg-david marked this pull request as draft November 2, 2026 15:58
…gets to hook ordering

demo-e2e-hooks failed its typecheck: ranHooksTool() returned an anonymous
class, which declaration emit cannot name (TS4094). It now returns ToolType.

Hook ordering and the observer chain run in the flow runner for every flow,
so create() + one tool grew by 6,639 bytes minified (2,747 gzipped). The
two browser bundle budgets move up by that much, keeping the headroom they
had; the Vite initial chunk stays within its budget.
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 15:58
@frontegg-david

Copy link
Copy Markdown
Contributor Author

CI on ca49c21 had two failures, both from this PR, fixed in f5ff8e7:

  • Typecheck (demo-e2e-hooks:typecheck): ranHooksTool() returned an anonymous class that declaration emit cannot name (TS4094). It now returns ToolType. nx run demo-e2e-hooks:typecheck passes.
  • E2E chunk 4 (demo-e2e-bundle-size): hook ordering and the observer chain run in the flow runner for every flow, so create() + one tool went from 1,310,872 to 1,317,511 bytes minified (+6,639; +2,747 gzipped) and create() + WebMcpPlugin + one tool to 1,324,700. That code cannot load on demand, so both budgets move up by the same amount, to 1,322,000 and 1,329,000, keeping the headroom they had. The Vite initial chunk (939,241 of 960,000) is unchanged. nx test demo-e2e-bundle-size: 10/10.

…k ordering measured

create() + one tool grew by 6,639 bytes minified and create() + WebMcpPlugin
+ one tool by 6,515, so the budgets are now 1,321,639 and 1,328,515.
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 16:11
@frontegg-david

Copy link
Copy Markdown
Contributor Author

Correction to the comment above: the budgets now move up by exactly each bundle's measured growth (529cbb1), to 1,321,639 (+6,639) and 1,328,515 (+6,515), as the PR body's Bundle size section states. nx test demo-e2e-bundle-size: 10/10.

@alexmercerpo
alexmercerpo merged commit 10819cd into main Nov 2, 2026
46 checks passed
@alexmercerpo
alexmercerpo deleted the feat/m07-hook-ordering branch November 2, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants