Skip to content

fix(mcp): prevent DetachedInstanceError in get_chart_preview#39921

Open
aminghadersohi wants to merge 2 commits intoapache:masterfrom
aminghadersohi:fix/mcp-detached-instance-chart-preview
Open

fix(mcp): prevent DetachedInstanceError in get_chart_preview#39921
aminghadersohi wants to merge 2 commits intoapache:masterfrom
aminghadersohi:fix/mcp-detached-instance-chart-preview

Conversation

@aminghadersohi
Copy link
Copy Markdown
Contributor

SUMMARY

get_chart_preview intermittently raised DetachedInstanceError when a Slice ORM object was loaded by find_chart_by_identifier() and then accessed after a session expire inside validate_chart_dataset().

Two changes:

  1. Eager session refresh — call db.session.refresh(chart) immediately after loading the Slice so all column attributes are populated in the object's __dict__ while the session is still active. This prevents DetachedInstanceError when strategy classes later access lazy attributes.

  2. SQLAlchemy error handler — add an explicit except SQLAlchemyError block inside _get_chart_preview_internal so any residual session errors (e.g. DetachedInstanceError) produce a clear ChartError with a "database session error, please retry" message instead of propagating to the outer wrapper with a generic message.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — backend-only fix.

TESTING INSTRUCTIONS

  1. Unit tests added in tests/unit_tests/mcp_service/chart/tool/test_get_chart_preview.py::TestDetachedInstanceError:
    • test_session_refresh_called_after_chart_load — verifies db.session.refresh() is called once after chart lookup
    • test_detached_instance_error_returns_chart_error — verifies DetachedInstanceError raised inside the preview strategy returns a ChartError (not an unhandled exception)
  2. Run: pytest tests/unit_tests/mcp_service/chart/tool/test_get_chart_preview.py::TestDetachedInstanceError -xvs

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

Call db.session.refresh() immediately after find_chart_by_identifier()
so the Slice object's attributes are loaded into memory while the session
is still active.  Without this, a commit or expire inside
validate_chart_dataset() can detach the object, causing
DetachedInstanceError when strategy classes access lazy attributes.

Also adds an explicit except SQLAlchemyError handler inside
_get_chart_preview_internal so that any residual session errors return a
clear ChartError with a "please retry" message rather than propagating
to the outer wrapper with a generic message.

Adds unit tests covering both the refresh call and the
DetachedInstanceError fallback path.

Closes #105716
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 14.28571% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.88%. Comparing base (5b5dd01) to head (546c805).

Files with missing lines Patch % Lines
...perset/mcp_service/chart/tool/get_chart_preview.py 14.28% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #39921      +/-   ##
==========================================
- Coverage   63.88%   63.88%   -0.01%     
==========================================
  Files        2583     2583              
  Lines      136604   136610       +6     
  Branches    31502    31503       +1     
==========================================
  Hits        87276    87276              
- Misses      47812    47818       +6     
  Partials     1516     1516              
Flag Coverage Δ
hive 39.38% <14.28%> (-0.01%) ⬇️
mysql 59.05% <14.28%> (-0.01%) ⬇️
postgres 59.13% <14.28%> (-0.01%) ⬇️
presto 41.08% <14.28%> (-0.01%) ⬇️
python 60.57% <14.28%> (-0.01%) ⬇️
sqlite 58.77% <14.28%> (-0.01%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aminghadersohi aminghadersohi requested a review from Copilot May 6, 2026 23:41
@aminghadersohi aminghadersohi marked this pull request as ready for review May 6, 2026 23:43
@dosubot dosubot Bot added the change:backend Requires changing the backend label May 6, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the MCP get_chart_preview tool against intermittent SQLAlchemy session detachment issues by proactively refreshing loaded Slice instances and converting SQLAlchemy exceptions into a user-friendly ChartError response.

Changes:

  • Refreshes the loaded chart ORM instance immediately after lookup to reduce the chance of later lazy-load access on a detached/expired instance.
  • Adds a dedicated except SQLAlchemyError handler to return a clearer, retry-oriented ChartError instead of bubbling a generic failure.
  • Adds unit tests covering the refresh call and DetachedInstanceErrorChartError behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
superset/mcp_service/chart/tool/get_chart_preview.py Refreshes chart after lookup and adds explicit SQLAlchemy exception handling for clearer error responses.
tests/unit_tests/mcp_service/chart/tool/test_get_chart_preview.py Adds tests asserting refresh invocation and that DetachedInstanceError is converted into a ChartError.

Comment thread superset/mcp_service/chart/tool/get_chart_preview.py Outdated
Comment thread tests/unit_tests/mcp_service/chart/tool/test_get_chart_preview.py Outdated
Comment thread superset/mcp_service/chart/tool/get_chart_preview.py Outdated
- Broaden the db.session.refresh() comment to describe SQLAlchemy's
  general expire-on-commit behaviour rather than pointing at a specific
  function as the source of detachment
- Switch logger.error() to logger.exception() in the SQLAlchemyError
  handler so the full stack trace is captured alongside the ctx.error
  message, aiding production debugging of intermittent session issues
- Replace the MagicMock URLPreview stub in the refresh test with a real
  URLPreview instance so Pydantic discriminated-union validation succeeds;
  add assertions that the response is a ChartPreview (not a ChartError)
  to confirm the happy path is actually exercised
@aminghadersohi
Copy link
Copy Markdown
Contributor Author

Thanks for the review! All three points addressed in the follow-up commit (546c805):

  1. Misleading comment — Removed the specific reference to validate_chart_dataset(). The comment now describes SQLAlchemy's general expire-on-commit behaviour as the source of detachment, without fingering a specific function.

  2. MagicMock URLPreview in test — Replaced the MagicMock stub with a real URLPreview instance so Pydantic's discriminated-union validation on ChartPreview.content passes cleanly. Added assertions that the response contains chart_id (not error_type) to confirm the happy path is actually exercised.

  3. logger.error drops traceback — Changed to logger.exception(...) so the full stack trace is captured alongside the ctx.error message.

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 7, 2026

Code Review Agent Run #2c0f2b

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: e2c6e7c..546c805
    • superset/mcp_service/chart/tool/get_chart_preview.py
    • tests/unit_tests/mcp_service/chart/tool/test_get_chart_preview.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants