Skip to content

fix(observe): preserve context-manager errors and cleanup - #1893

Open
1fanwang wants to merge 4 commits into
langfuse:mainfrom
1fanwang:1fannnw/preserve-observed-generator-protocol
Open

1fanwang wants to merge 4 commits into
langfuse:mainfrom
1fanwang:1fannnw/preserve-observed-generator-protocol

Conversation

@1fanwang

@1fanwang 1fanwang commented Sep 19, 2026

Copy link
Copy Markdown

What does this PR do?

An observed context-manager generator replaces the application's error with AttributeError and leaves its file open. This preserves the generator protocol under the tracing context, so exception handling and cleanup still run.

Interrupt and process-exit exceptions also finish their observations and reach the caller unchanged.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactor
  • Documentation update
  • Tooling, CI, or repo maintenance

Verification

Testing Done

The reproducer is in the regression tests in this diff. It uses the real Langfuse client, an in-memory span exporter and real temporary files. It does not mock the tracing runtime or call a server or model.

Scenario Before After
Sync and async context-manager failures Application error is masked; file stays open; no finished span Application error is preserved; file closes; span finishes
The generator handles the error Error handler is never reached Error is suppressed; file closes; span finishes

The same tests ran against the fetched main commit with only the test file copied over, then against this branch.

Reproduction commands
set -e
uv sync --locked --python 3.12
CHECKOUT=$PWD
TEST=tests/unit/test_observe.py
BASELINE=$(mktemp -d)
git worktree add --detach "$BASELINE" 65392c73731e07711828745de337fdf7bba31fbc
cp "$TEST" "$BASELINE/$TEST"
set +e
(
  cd "$BASELINE" &&
  UV_PROJECT_ENVIRONMENT="$CHECKOUT/.venv" uv run --no-sync python -m pytest \
    "$TEST" -k context_manager_preserves_exception_handling \
    -n 0 -q --tb=short --log-cli-level=INFO
)
BASELINE_EXIT=$?
set -e
test "$BASELINE_EXIT" -eq 1
uv run --no-sync python -m pytest "$TEST" \
  -k context_manager_preserves_exception_handling \
  -n 0 -q --tb=short --log-cli-level=INFO

Before and after, prior to forced cleanup:

INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=False error=AttributeError("'_ContextPreservedSyncGeneratorWrapper' object has no attribute 'throw'") resource_closed=False finished_spans=0
INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=True error=AttributeError("'_ContextPreservedSyncGeneratorWrapper' object has no attribute 'throw'") resource_closed=False finished_spans=0
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=False error=AttributeError("'_ContextPreservedAsyncGeneratorWrapper' object has no attribute 'athrow'") resource_closed=False finished_spans=0
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=True error=AttributeError("'_ContextPreservedAsyncGeneratorWrapper' object has no attribute 'athrow'") resource_closed=False finished_spans=0
exit=1

After, prior to forced cleanup:
INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=False error=ValueError('application failed') resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=True error=None resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=False error=KeyboardInterrupt('application failed') resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:100 sync suppress=False error=SystemExit('application failed') resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=False error=ValueError('application failed') resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=True error=None resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=False error=KeyboardInterrupt('application failed') resource_closed=True finished_spans=1
INFO     tests.unit.test_observe:test_observe.py:157 async suppress=False error=SystemExit('application failed') resource_closed=True finished_spans=1
exit=0

Checklist

  • I self-reviewed the diff using code_review.md.
  • I added or updated tests for behavior changes.
  • I updated docs, examples, or .env.template if needed.
  • I did not hand-edit generated files; if generated files changed, I used the upstream regeneration path.
  • I did not commit secrets or credentials.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>

@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 19, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment thread langfuse/_client/observe.py
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
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