Skip to content

feat: add LangfuseError exception hierarchy - #1908

Open
Yoel-qin wants to merge 2 commits into
langfuse:mainfrom
Yoel-qin:feat/langfuse-error-hierarchy-906
Open

Yoel-qin wants to merge 2 commits into
langfuse:mainfrom
Yoel-qin:feat/langfuse-error-hierarchy-906

Conversation

@Yoel-qin

@Yoel-qin Yoel-qin commented Sep 29, 2026 •

Copy link
Copy Markdown

Summary

Closes #906 (originally filed against the old SDK; rescoped to the current codebase after mapping the raise sites).

Since the original issue, the SDK has gained typed error classes in hand-written code (APIError, APIErrors in langfuse/_utils/request.py, RegressionError in langfuse/experiment.py), but they share no common base, only one of them was importable from the package root, and one bare raise Exception remained in auth_check(). This PR makes the SDK's errors precisely catchable:

  • New langfuse/errors.py defining LangfuseError(Exception) and AuthError(LangfuseError).
  • APIError, APIErrors, and RegressionError now derive from LangfuseError — purely additive, every class remains an Exception, so existing except Exception handlers keep working.
  • auth_check() raises AuthError instead of a bare Exception when the credentials resolve to no project (docstring updated).
  • LangfuseError, AuthError, APIError, APIErrors are exported from the package root and listed in __all__.

Errors raised by the generated API client (langfuse.api) are intentionally left untouched (generated code); this is documented in the new module's docstring.

Testing

  • New tests/unit/test_errors.py: hierarchy/inheritance assertions, str() output unchanged for APIError/APIErrors, and auth_check() behavior (raises AuthError catchable as LangfuseError on empty project list, returns True otherwise) via a mocked api.projects.get.
  • uv run --frozen pytest -n auto --dist worksteal tests/unit: 685 passed with this change vs 679 on the baseline (the +6 are the new tests); the same 3 pre-existing Windows-environment failures and 18 pre-existing setup errors occur with and without this change.
  • uv run --frozen ruff check ., ruff format (changed files), and uv run --frozen mypy langfuse --no-error-summary all clean.

RetriggerConfidence Score: 4/5

The PR is not ready to merge while invalid credentials bypass the new authentication error hierarchy.

Summary

The PR adds a hand-written SDK exception hierarchy, makes existing exceptions inherit from it, exports several error classes, and changes the empty-project auth_check() failure to AuthError.

  • The authentication check still exposes a generated error for invalid credentials.
  • The root-level APIError export needs a clearer distinction from generated public API errors.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[auth_check] --> B[Generated projects.get]
  B -->|Projects returned, empty| C[AuthError]
  B -->|HTTP 401| D[Generated UnauthorizedError]
  C --> E[LangfuseError]
  D --> F[Generated ApiError]
Loading

Reviews (1) · Last reviewed commit: "feat: add LangfuseError exception hierar..."

Introduce a LangfuseError base class in langfuse/errors.py and derive all
hand-written SDK exceptions from it: APIError, APIErrors, and
RegressionError. Replace the last bare raise Exception in auth_check() with
a new AuthError subclass. Export the hierarchy from the package root so
applications can catch SDK errors precisely with 'except LangfuseError'
while existing 'except Exception' handlers keep working unchanged.

Closes langfuse#906

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@CLAassistant

CLAassistant commented Sep 29, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment on lines +3512 to 3514
raise AuthError(
"Auth check failed, no project found for the keys provided."
)

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.

P1 Invalid credentials bypass AuthError When credentials are invalid, projects.get() raises the generated UnauthorizedError before this new AuthError branch runs. UnauthorizedError is not a LangfuseError, and the existing handler catches a different generated error class. Callers using the new hierarchy therefore cannot catch this authentication failure. Handle the 401 response as AuthError at this boundary.

Knowledge Base Used: API client and service surface

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/_client/client.py
Line: 3512-3514

Comment:
**Invalid credentials bypass AuthError** When credentials are invalid, `projects.get()` raises the generated `UnauthorizedError` before this new `AuthError` branch runs. `UnauthorizedError` is not a `LangfuseError`, and the existing handler catches a different generated error class. Callers using the new hierarchy therefore cannot catch this authentication failure. Handle the 401 response as `AuthError` at this boundary.

**Knowledge Base Used:** [API client and service surface](https://app.greptile.com/personal-org-4986/-/custom-context/knowledge-base/langfuse/langfuse-python/-/docs/api-client-and-service-surface.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in a57ebff, and it was subtler than the diff suggested: the generated Error class the handler caught is itself just the generic 400 subclass of the fern ApiError base, so UnauthorizedError (a sibling subclass) escaped the handler entirely and reached callers raw, skipping even the handle_fern_exception logging.

auth_check() now catches ApiError instead, raises a chained AuthError for UnauthorizedError, and re-raises everything else unchanged (so non-auth fern errors keep their exact types). Added tests for the 401 → AuthError mapping and for a NotFoundError propagating unchanged.

Note: the same except Error pattern exists at a few other call sites in client.py (score/trace-tag helpers). I left those untouched to keep this PR scoped to the auth boundary — happy to do a separate pass converting them if that's wanted.

Invalid credentials never reached the AuthError branch: api.projects.get()
raises the generated UnauthorizedError, which is not a subclass of the
generated Error class that auth_check() caught, so it escaped the handler
entirely. Catch the fern ApiError base instead, raise AuthError (chained)
for UnauthorizedError, and re-raise everything else unchanged. Widen
handle_fern_exception/generate_error_message_fern annotations to ApiError
to match what they already handle at runtime.

This branch has not been deployed

No deployments
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.

2 participants