getredash/redash · error · PermissionError
Query id {} not found.
Error message
Query id {} not found. What it means
Raised by _load_query (redash/query_runner/query_results.py:49) as a PermissionError when the requested query exists but belongs to a different organization than the requesting user. Redash is multi-tenant by org_id, and cross-org access is treated as 'not found' to avoid leaking the existence of queries in other tenants.
Source
Thrown at redash/query_runner/query_results.py:49
def extract_query_params(query):
return re.findall(r"(?:join|from)\s+param_query_(\d+)_{([^}]+)}", query, re.IGNORECASE)
def extract_query_ids(query):
queries = re.findall(r"(?:join|from)\s+query_(\d+)", query, re.IGNORECASE)
return [int(q) for q in queries]
def extract_cached_query_ids(query):
queries = re.findall(r"(?:join|from)\s+cached_query_(\d+)", query, re.IGNORECASE)
return [int(q) for q in queries]
def _load_query(user, query_id):
query = models.Query.get_by_id(query_id)
if user.org_id != query.org_id:
raise PermissionError("Query id {} not found.".format(query.id))
# TODO: this duplicates some of the logic we already have in the redash.handlers.query_results.
# We should merge it so it's consistent.
if not has_access(query.data_source, user, view_only):
raise PermissionError("You do not have access to query id {}.".format(query.id))
return query
def replace_query_parameters(query_text, params):
qs = parse_qs(params)
for key, value in qs.items():
query_text = query_text.replace("{{{{{my_key}}}}}".format(my_key=key), value[0])
return query_text
def get_query_results(user, query_id, bring_from_cache, params=None):
query = _load_query(user, query_id)View on GitHub (pinned to ca79fe988d)
Solutions
- Verify the query id belongs to the same Redash instance/account the user is on (open /queries/<id> in the UI)
- Update the hardcoded query id in the calling query/python script to one from the current org
- In tests, create both the user and the query under the same org (models.Query created with the user's org)
Example fix
# before (test) user = factory.create_user(org=factory.create_org()) query = factory.create_query(org=self.org) # after user = factory.create_user(org=self.org) query = factory.create_query(org=self.org)
Defensive patterns
Strategy: try-catch
Validate before calling
query = models.Query.get_by_id(query_id)
if query is None or query.org_id != user.org_id:
return error_response(404, 'query not found') Try / catch
try:
results = get_query_results(user, qid, bring_from_cache)
except PermissionError as e:
return error_response(404, str(e)) # treat cross-org as not found
except Exception as e:
return error_response(400, str(e)) Prevention
- Store query ids alongside the org/instance they came from; never hardcode cross-instance ids
- In tests, always create user and query with factory helpers that share the default org
- Validate ids from user input against the current org before calling loaders
When it happens
Trigger: Calling get_query_results(user, query_id, ...) or the Python `query_results` query runner with a query id whose org_id differs from user.org_id — e.g. a query id copied from another Redash instance or another account's URL.
Common situations: Copy-pasting a query id or full query URL between staging/production or between two separate Redash installations; stale hardcoded query ids after a data migration or org restructure; tests that create users and queries under different orgs.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- You do not have access to data source: %s.
- You do not have access to query id %s.
- Missing `rows` field in `result` dict.
- Missing `columns` field in `result` dict.
- `rows` field should be of type `list`.
AI-assisted analysis of getredash/redash@ca79fe988d (2026-08-28).
Data as JSON: /api/errors/be0c7a7d1fd1b6de.
Report an issue: GitHub.