Skip to content

fix: wrap postgres health check SQL in sqlalchemy.text() - #805

Open
ApoorvThite wants to merge 7 commits into
ascherj:mainfrom
ApoorvThite:fix/154-health-check-sql-text-wrap
Open

fix: wrap postgres health check SQL in sqlalchemy.text()#805
ApoorvThite wants to merge 7 commits into
ascherj:mainfrom
ApoorvThite:fix/154-health-check-sql-text-wrap

Conversation

@ApoorvThite

Copy link
Copy Markdown

Summary

GET /health always reported PostgreSQL as unhealthy, even when the database was fully reachable. The probe called db.execute("SELECT 1") with a bare Python string, but SQLAlchemy 2.x's AsyncSession.execute() only accepts an Executable (e.g. sqlalchemy.text(...)), so the call raised ArgumentError on every invocation. That exception was swallowed by the surrounding try/except, which then marked dependencies.postgres (and the overall status) as "unhealthy" regardless of the database's real state — a false negative that made the endpoint useless for monitoring. This PR wraps the query in sqlalchemy.text("SELECT 1") so the probe actually executes and reports the database's true status.

Issue

Closes #154

Changes

  • api/routes/health.py: import sqlalchemy.text and wrap the Postgres probe's query (db.execute(text("SELECT 1"))) so it's accepted by SQLAlchemy 2.x's AsyncSession.execute().
  • tests/integration/test_health_check.py: updated the existing reproduction test to assert dependencies.postgres == "healthy" against a live, working session (previously asserted the buggy "unhealthy" result). Added a second test asserting no postgres_health_check_failed log event fires, so a future regression back to a bare string is caught even if the string assertion is ever loosened.

Testing

  • Unit tests pass (make test-unit)
  • Integration tests pass (make test-integration for tests/integration/test_health_check.py, run directly via .venv/bin/pytest tests/integration/test_health_check.py -m integration)
  • Linter passes (make lint)
  • Type checker passes (make typecheck)
  • New/updated tests cover the changes

Pre-existing failures (not introduced by this change)

This environment doesn't have the Docker/Postgres stack from make setup available, so I verified the fix against an in-memory SQLite AsyncSession (matching the existing reproduction test's approach) rather than real Postgres.

I confirmed the following failures exist on main before my change and are unaffected by it (checked via git stash diffing before/after):

  • make test-unit: 53 pre-existing failures, all unrelated to health.py — async-mock setup issues in test_review_service.py (AttributeError: 'coroutine' object has no attribute 'first'), and assertion/fixture issues in test_pii_scrubber.py, test_resume_parser.py, test_readme_parser.py, test_tech_detector.py, test_skill_extractor.py, and others. My change touches none of these modules; 375 tests pass either way.
  • ruff check .: exactly 183 pre-existing errors both before and after my change (verified byte-for-byte identical count via git stash).
  • mypy api/routes/health.py: exactly 11 pre-existing errors both before and after my change, including one directly relevant to this issue: "Settings" has no attribute "redis_host"/"redis_port" — the Redis leg of this same health check references config fields that don't exist on Settings (core/config.py only defines redis_url), so it always raises AttributeError and reports unhealthy. This means /health will still return 503 overall after this PR merges, purely from the Redis leg — that's a separate, out-of-scope bug from Health check DB probe passes a raw SQL string, which fails under SQLAlchemy 2.x #154's title (which is specifically about the Postgres probe's SQL string). I'm planning to file it as a follow-up issue rather than fold it into this PR.

Screenshots / Demo

N/A — backend-only change to a JSON health check endpoint. Verified via the integration tests above (both pass, and the second test's log-event assertion is a proxy for what a curl /health inspection would show: no postgres_health_check_failed event, dependencies.postgres: "healthy").

Notes for Reviewers

  • The fix itself is a one-line change (db.execute("SELECT 1")db.execute(text("SELECT 1"))); most of this PR's size is the updated/added tests.
  • Please weigh in on scope: should the Redis settings.redis_host/redis_port bug (noted above) be fixed in this PR, or is a separate follow-up issue the right call? I've leaned toward splitting it out since it's unrelated to Health check DB probe passes a raw SQL string, which fails under SQLAlchemy 2.x #154's title and the Postgres fix is independently correct and testable.
  • I wasn't able to run this against a real Postgres instance in my dev environment (no Docker available) — if a reviewer can confirm behavior against the docker-compose Postgres, that would close the loop on the "Test environment" risk noted in PLAN.md.

Apoorv Thite and others added 6 commits July 19, 2026 22:22
…robe

Add tests/integration/test_health_check.py: calls health_check() with a
working in-memory SQLite AsyncSession and asserts the postgres leg is
still reported unhealthy. This documents the bug — db.execute("SELECT 1")
passes a bare string, which SQLAlchemy 2.x rejects with ArgumentError,
and that exception is swallowed and misreported as the DB being down.

Also adds aiosqlite as a dev dependency so the test can run without a
live Postgres instance, and adds PLAN.md with the fix approach.
SQLAlchemy 2.x rejects bare strings for textual SQL, so
db.execute("SELECT 1") raised ArgumentError inside the health check's
Postgres probe. That exception was swallowed by the surrounding
try/except and misreported as the database being unhealthy, so /health
always failed the Postgres leg regardless of real DB state.

Wraps the query in sqlalchemy.text() so the probe executes correctly.

Fixes ascherj#154

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ApoorvThite
ApoorvThite marked this pull request as ready for review August 4, 2026 22:50
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.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.

Health check DB probe passes a raw SQL string, which fails under SQLAlchemy 2.x

1 participant