projectdiscovery/katana · error

failed to navigate back to origin page: %s != %s

Error message

failed to navigate back to origin page: %s != %s

What it means

After history-back or shortest-path navigation, isCorrectNavigation compares the current page hash to action.OriginID; when they differ, navigation did not land back on the origin page. This means the page content/hash still mismatches the origin even after the recovery strategies (SimHash tolerance already failed). The caller treats the action as not completing correctly.

Source

Thrown at pkg/engine/headless/crawler/state.go:51

	originPageState, err := c.crawlGraph.GetPageState(action.OriginID)
	if err != nil {
		return "", pageState, fmt.Errorf("failed to get origin page state: %w", err)
	}

	if pageState != nil && originPageState != nil {
		distance := simhash.Distance(pageState.SimHash, originPageState.SimHash)
		if distance <= simhashThreshold {
			c.logger.Debug("Page is similar enough to origin, proceeding",
				slog.String("current_hash", currentPageHash),
				slog.String("origin_hash", action.OriginID),
				slog.Uint64("simhash_distance", uint64(distance)),
			)
			// Treat this page as the origin state to avoid creating a new vertex
			return originPageState.UniqueID, pageState, nil
		}
	}

	return "", pageState, fmt.Errorf("failed to navigate back to origin page: %s != %s", currentPageHash, action.OriginID)
}

func getPageHash(page *browser.BrowserPage) (string, *types.PageState, error) {
	pageState, err := newPageState(page, nil)
	if err == ErrEmptyPage {
		return emptyPageHash, nil, nil
	}
	if err != nil {
		return "", nil, errors.Wrap(err, "could not get page state")
	}
	return pageState.UniqueID, pageState, nil
}

var ErrEmptyPage = errors.New("page is empty")

func newPageState(page *browser.BrowserPage, action *types.Action) (*types.PageState, error) {
	pageInfo, err := page.Info()
	if err != nil {

View on GitHub (pinned to e3e742739c)

Solutions

  1. Broaden the simhashThreshold or normalize volatile page content before hashing so dynamic pages match their origin state.
  2. Check whether the site redirects or invalidates sessions and re-authenticate/avoid actions on such pages.
  3. Verify OriginID matches the hashing scheme used by getPageHash/newPageState (same normalization on both sides).
  4. Log currentPageHash vs action.OriginID at debug level to identify which normalization difference causes the mismatch.

Example fix

// before
return "", pageState, fmt.Errorf("failed to navigate back to origin page: %s != %s", currentPageHash, action.OriginID)
// after
if strings.HasPrefix(currentPageHash, action.OriginID) || isKnownDynamicPage(page) {
    c.logger.Debug("accepting approximate origin match", "hash", currentPageHash, "origin", action.OriginID)
    return action.OriginID, pageState, nil
}
return "", pageState, fmt.Errorf("failed to navigate back to origin page: %s != %s", currentPageHash, action.OriginID)
Defensive patterns

Strategy: fallback

Validate before calling

// before relying on exact match, sanity-check similarity
if simhash.Distance(current.SimHash, origin.SimHash) > simhashThreshold {
    log.Printf("warning: origin mismatch, hash=%s origin=%s", currentHash, originID)
}

Type guard

func matchesOrigin(currentHash, originID string) bool {
    return currentHash == originID
}

Try / catch

ok, err := crawler.isCorrectNavigation(action)
if err != nil {
    if strings.Contains(err.Error(), "failed to navigate back to origin page") {
        // treat action as failed, snapshot page for debugging, continue crawl
    }
}

Prevention

When it happens

Trigger: tryBrowserHistoryNavigation or tryShortestPathNavigation executes, getPageHash returns currentPageHash, SimHash distance > threshold, and currentPageHash != action.OriginID — i.e., the browser ended on a different page/state than the origin vertex.

Common situations: Dynamic pages whose hash changes on every render (timestamps, CSRF tokens, A/B variants); history.back() blocked by SPA routing or site redirects; login/session expiry redirecting away from the origin; origin ID computed from a different rendering (headless vs first crawl).

Related errors


AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03). Data as JSON: /api/errors/1a1c9da826f38a6a. Report an issue: GitHub.