getredash/redash · error · Exception

{} did not change; possible misconfiguration

Error message

{} did not change; possible misconfiguration

What it means

TokenPagination.next() guards against infinite pagination loops: if the token extracted from the response equals the token already sent in the request params, the endpoint is ignoring your token parameter and the same page would be fetched forever, so it raises this generic Exception. The field named in the message is the response field that didn't change.

Source

Thrown at redash/query_runner/json_ds.py:277

        return True, next_url, request_options


class TokenPagination(RequestPagination):
    def __init__(self, pagination):
        self.fields = pagination.get("fields", ["next_page_token", "page_token"])
        if not isinstance(self.fields, list) or len(self.fields) != 2:
            raise QueryParseError("'pagination.fields' should be a list of 2 field names")

    def next(self, url, request_options, response):
        next_token = _apply_path_search(response, self.fields[0], "")
        if not next_token:
            return False, None, request_options

        params = request_options.get("params", {})

        # prevent infinite loop that can happen if self.fields[1] is wrong
        if next_token == params.get(self.fields[1]):
            raise Exception("{} did not change; possible misconfiguration".format(self.fields[0]))

        params[self.fields[1]] = next_token
        request_options["params"] = params
        return True, url, request_options


register(JSON)

View on GitHub (pinned to ca79fe988d)

Solutions

  1. Fix pagination.fields[1] to the exact query parameter the API reads for the next-page token
  2. Fix pagination.fields[0] to the exact response field containing the token (a wrong field can yield a constant value)
  3. If the API paginates via URL in the body, switch pagination.type to "url"
  4. If the token must go in a header or path, this runner can't express it — fetch via a different approach

Example fix

// before
{"pagination": {"type": "token"}}
// after
{"pagination": {"type": "token", "fields": ["next_cursor", "cursor"]}}
Defensive patterns

Strategy: fallback

Validate before calling

# Inspect one real response first: confirm the token field changes per page
import requests
r1 = requests.get(url); r2 = requests.get(url, params={req_param: token1})
assert r1.json()[resp_field] != r2.json()[resp_field], 'API ignores the token param'

Try / catch

try:
    run_json_query(query)
except Exception as e:
    if 'did not change; possible misconfiguration' in str(e):
        # fix pagination.fields[1] / [0], don't retry blindly
        log_and_flag_pagination_config()

Prevention

When it happens

Trigger: pagination.fields[1] (request param name) doesn't match what the API actually accepts (e.g. you send page_token but the API expects cursor), so the API returns the same next-token every page. Also triggered when the response field always returns a constant sentinel token.

Common situations: Copied default fields ['next_page_token', 'page_token'] but the API uses different parameter naming; or the API requires the token in a header/path rather than a query param, which this pagination type can't express.

Related errors


AI-assisted analysis of getredash/redash@ca79fe988d (2026-08-28). Data as JSON: /api/errors/fc980236b9d26b32. Report an issue: GitHub.