Inspiration

Data teams live in Slack. We paste SQL snippets, ask “can we ship this?”, and debate SELECT * in threads. Generic LLM chatbots answer from vibes — inventing table names, guessing lineage, and sounding confident while being wrong.

We built SQL Second Opinion for the moment someone drops SQL in a channel and the team needs facts, fast. No warehouse credentials. No hallucinated schema. Only what a real SQL parser can prove.

The idea grew out of sqlucent / SQL X-Ray — a deterministic SQL analysis engine we had already built. The hackathon pushed us to put that engine where the conversation already happens: Slack.

What it does

SQL Second Opinion is a Slack bot that gives a parser-backed second opinion on any SQL you paste.

Three ways to use it:

Entry point How
Modal /sql → paste multi-line SQL, pick dialect, optional schema DDL
Inline /sql SELECT 1 → one-liner, no form
Message shortcut Right-click a message with sql …Analyze SQL in message

Every response includes three sections, all from the parser — not from model guessing:

  1. Walkthrough — step-by-step execution (CTEs, joins, final SELECT)
  2. Lineage — column-level provenance (e.g. net_revenuequantity * unit_price)
  3. Risks (lint) — deterministic smells like SELECT *, missing filters, dialect issues

Optional schema DDL lets SELECT * expand to real column names. No database connection is required; users paste text in, and structured analysis comes back in the thread.

We also ship a thin MCP server (explain_sql) so the same analysis is available in Cursor and other MCP clients — useful for hackathon compliance and local development.

How we built it

Slack (/sql · modal · shortcut)
        │
        ▼  HTTPS POST /slack/events
Slack Bolt (Python) on Render
        │
        ▼  subprocess
sqlucent CLI  (--walkthrough · --lineage · --lint)
        │
        ▼
Formatted reply → channel thread

Stack:

  • Slack Bolt (Python) — slash command, modal, message shortcut, interactivity
  • Flask — HTTP adapter for Render (Socket Mode for local dev)
  • sqlucent (PyPI) — deterministic analysis via subprocess; same engine as SQL X-Ray
  • Render — production hosting at sql-second-opinion-slack.onrender.com
  • MCP — stdio server exposing explain_sql for Cursor / Claude Desktop

Repo layout:

  • app/main.py — Bolt handlers, lazy listeners for Slack’s 3-second ack rule
  • app/runner.py — sqlucent subprocess wrapper
  • app/modal.py — modal JSON for /sql
  • app/format_reply.py — Slack markdown formatting
  • mcp_server/ — thin MCP wrapper reusing analyze_sql()

Golden rule: every table and column name in a reply comes from sqlucent output — never from an LLM inventing schema.

Challenges we ran into

  1. Slack’s 3-second rule — sqlucent analysis can exceed Slack’s slash-command timeout. We split handlers into immediate ack() plus lazy background work, and open modals before ack on /sql.

  2. Socket Mode vs HTTP — local dev used Socket Mode (SLACK_APP_TOKEN); production on Render needed HTTP with Request URLs. With Socket Mode on, Slack never POSTs to Render — we saw zero logs until we turned Socket Mode off and fixed the URL path to /slack/events (not /events).

  3. Render deploy friction — Python 3.14 default (Render ignores runtime.txt), gunicorn ModuleNotFoundError and unsupported --factory, free-tier cold starts causing operation_timeout. We pinned Python 3.11 via .python-version + PYTHON_VERSION, and simplified to python -m app.main with threaded Flask.

  4. Trust by design — we deliberately avoided “ask the LLM about this SQL.” The product value is deterministic parser output in the channel where the conversation already happens.

Accomplishments that we're proud of

  • End-to-end Slack flow works/sql, modal, inline, and message shortcut all return walkthrough, lineage, and lint in production on Render.
  • Facts-only analysis — no warehouse connection, no hallucinated table names; every claim is traceable to sqlucent parser output.
  • Meets you where you work — three entry points cover “I want to paste SQL”, “quick probe”, and “someone already posted SQL in the thread”.
  • Thin, focused codebase — Bolt app + sqlucent subprocess + optional MCP; no over-engineered agent framework.
  • Reusable engine — same sqlucent core as SQL X-Ray; Slack is one surface, MCP is another.

What we learned

  • Slack delivery mode matters — Socket Mode and HTTP are mutually exclusive configurations; mixing them (Socket Mode on in Slack, HTTP on Render) fails silently with no server logs.
  • Slash commands need fast acks — long-running work must run after ack(), via lazy listeners or background threads, not inside the initial HTTP handler.
  • PaaS defaults change — Render’s Python 3.14 default and PATH quirks for gunicorn taught us to pin versions explicitly and prefer boring, working start commands.
  • Paste-in beats connect — for SQL review in chat, users don’t want to configure warehouse credentials; parser-on-text is enough for walkthrough, lineage, and lint.
  • Hackathon scope discipline — MCP for compliance + dev ergonomics, but the Slack bot is the product judges actually test in the sandbox.

What's next for SQL Second Opinion

  • Keep Render warm — uptime ping or Starter tier so judges don’t hit cold-start timeouts during review.
  • Richer lint catalog — expose more sqlucent rules and severity grouping in Slack blocks (sections, dividers, emoji by severity).
  • Schema from thread context — optional: detect CREATE TABLE in nearby messages and auto-attach as schema DDL.
  • Dialect auto-detect — hint dialect from code fence language tag (`snowflake).
  • Slack Agent for Organizations — package for marketplace install with admin controls and workspace-wide deployment.
  • Deeper MCP integration — wire explain_sql into CI or Cursor rules so the same engine reviews SQL before it lands in a PR thread.

Built With

Share this project:

Updates