Skip to content

feat(jql): explain the opaque 404 from the public JQL API - #767

Open
JJwilkin wants to merge 1 commit into
stagingfrom
joshua/jql-not-enabled-error
Open

feat(jql): explain the opaque 404 from the public JQL API#767
JJwilkin wants to merge 1 commit into
stagingfrom
joshua/jql-not-enabled-error

Conversation

@JJwilkin

@JJwilkin JJwilkin commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

📝 Summary

A valid JQL query from an organization without the public_jql feature flag raised a bare JudgmentAPIError: 404: Resource not found, giving callers nothing to act on.

  • 1. Added JudgmentJQLUnavailableError (subclass of JudgmentAPIError) for 404s on JQL routes, exported from judgeval.exceptions
  • 2. Mapped the 404 in _run_jql — the single choke point covering query, present, and discover
  • 3. Sharpened the message to Project '<name>' was not found for this organization. when the server detail is Project not found
  • 4. Added regression coverage, including a test pinning that non-JQL 404s are left alone

Before

JudgmentAPIError: 404: Resource not found

After

JudgmentJQLUnavailableError: 404: JQL is not enabled for this organization, or
project 'my-project' was not found — the API returns the same 404 for both.
Contact Judgment to enable JQL for your organization.

Why the message names two causes

judgeval-server throws NotFoundError('Resource not found') when the public_jql flag is off, which is byte-identical to the 404 for a missing project, and the published OpenAPI documents 404 as "Project or public JQL endpoint not found". The SDK therefore cannot positively identify the disabled flag, and claiming it could would mislead the much more common case of a typo'd project name. Naming both causes plus the next step is the honest maximum available client-side.

One string does discriminate: the org-level gate runs before the project lookup server-side, so a Project not found detail can only come from an already-enabled org. That is used to sharpen the message, with a comment marking the coupling and a safe fallback if the server text ever changes.

Design notes

  • The mapping is in _run_jql, not in map_judgment_api_error's status table — that mapper is shared with datasets/ and offline_tests/, where a 404 has nothing to do with JQL.
  • The new error subclasses JudgmentAPIError, so existing except JudgmentAPIError callers are unaffected (asserted in a test).
  • No new attributes on JudgmentAPIError, so the exact-vars() assertions in test_jql.py still hold.
  • The parity-gated _generated_contract.py / _generated_transport.py are untouched.

Follow-up (not in this PR)

Worth deciding separately: have judgeval-server send a distinguishable error code for the flag-off case, letting both SDKs state the cause outright instead of inferring it. If the current ambiguity is intentional non-disclosure, leave it as is.

✅ Checklist

Testing

  • uv run pytest src/tests643 passed
  • uv run mypy ./src/judgeval/ → clean (196 files)
  • uv run ruff check . / ruff format --check . → clean
  • New tests: opaque-404 message, sharpened missing-project message, except JudgmentAPIError back-compat, and non-JQL 404 left unmapped

🤖 Generated with Claude Code


Open in Devin Review

A valid JQL query against an organization without the `public_jql` feature
flag raised a bare `JudgmentAPIError: 404: Resource not found`, which gave
callers nothing to act on.

The server answers 404 identically whether the feature is disabled or the
project does not exist, so the SDK cannot positively identify either cause.
Raise a new `JudgmentJQLUnavailableError` that names both and points at the
fix. Because the organization-level gate runs before the project lookup
server-side, a `Project not found` detail can only come from an enabled
organization, so use it to sharpen the message and fall back to naming both
causes otherwise.

The mapping lives in `_run_jql` rather than `map_judgment_api_error` because
that mapper is shared with datasets and offline tests, where a 404 is
unrelated to JQL. The new error subclasses `JudgmentAPIError`, so existing
`except JudgmentAPIError` callers are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

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.

1 participant