GoogleChrome/lighthouse · error · Error

Navigation already in progress

Error message

Navigation already in progress

What it means

State-machine guard in UserFlow.navigate(). navigate() requires currentNavigation to be undefined. This throw fires when a navigation is already in progress — startNavigation() was called but endNavigation() has not completed. Since startNavigation() internally calls navigate(), this also guards against overlapping startNavigation calls.

Source

Thrown at core/user-flow.js:128

   * @param {LH.Gatherer.GatherResult} gatherResult
   * @param {LH.UserFlow.StepFlags} [flags]
   */
  _addGatherStep(gatherResult, flags) {
    const gatherStep = {
      artifacts: gatherResult.artifacts,
      flags,
    };
    this._gatherSteps.push(gatherStep);
    this._gatherStepRunnerOptions.set(gatherStep, gatherResult.runnerOptions);
  }

  /**
   * @param {LH.NavigationRequestor} requestor
   * @param {LH.UserFlow.StepFlags} [flags]
   */
  async navigate(requestor, flags) {
    if (this.currentTimespan) throw new Error('Timespan already in progress');
    if (this.currentNavigation) throw new Error('Navigation already in progress');

    const nextFlags = this._getNextNavigationFlags(flags);
    const gatherResult = await navigationGather(this._page, requestor, {
      config: this._options?.config,
      flags: nextFlags,
    });

    this._addGatherStep(gatherResult, nextFlags);
  }

  /**
   * This is an alternative to `navigate()` that can be used to analyze a navigation triggered by user interaction.
   * For more on user triggered navigations, see https://github.com/GoogleChrome/lighthouse/blob/main/docs/user-flows.md#triggering-a-navigation-via-user-interactions.
   *
   * @param {LH.UserFlow.StepFlags} [stepOptions]
   */
  async startNavigation(stepOptions) {
    /** @type {(value: () => void) => void} */

View on GitHub (pinned to 9515cd4e58)

Solutions

  1. Call flow.endNavigation() before starting a new navigation
  2. Ensure the page navigation actually occurs between startNavigation and endNavigation — endNavigation awaits the result
  3. Wrap navigation logic in try/finally to guarantee endNavigation() runs

Example fix

// before — overlapping navigations
await flow.startNavigation();
// ... user clicks a link triggering navigation ...
await flow.startNavigation(); // throws

// after — end first navigation
await flow.startNavigation();
// ... user clicks a link triggering navigation ...
await flow.endNavigation();
await flow.startNavigation();
Defensive patterns

Strategy: validation

Validate before calling

if (flow.currentNavigation) {
  await flow.endNavigation();
}
await flow.navigate(url);

Type guard

/** @param {UserFlow} flow @returns {boolean} */
function isNavigationActive(flow) {
  return flow.currentNavigation != null;
}

Try / catch

try {
  await flow.navigate(url);
} catch (e) {
  if (e.message === 'Navigation already in progress') {
    await flow.endNavigation();
    await flow.navigate(url);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling flow.navigate(url) or flow.startNavigation() after a prior flow.startNavigation() without calling flow.endNavigation() first. The currentNavigation property is still set from the prior startNavigation call.

Common situations: Forgot to call endNavigation() after startNavigation(). The user interaction that triggers the navigation was never performed, so endNavigation was never called. Calling navigate() (the synchronous variant) while an async startNavigation flow is pending.

Related errors


AI-assisted analysis of GoogleChrome/lighthouse@9515cd4e58 (2026-08-13). Data as JSON: /api/errors/baa94e24cfe445f2. Report an issue: GitHub.