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:
- Walkthrough — step-by-step execution (CTEs, joins, final SELECT)
- Lineage — column-level provenance (e.g.
net_revenue←quantity * unit_price) - 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_sqlfor Cursor / Claude Desktop
Repo layout:
app/main.py— Bolt handlers, lazy listeners for Slack’s 3-second ack ruleapp/runner.py— sqlucent subprocess wrapperapp/modal.py— modal JSON for/sqlapp/format_reply.py— Slack markdown formattingmcp_server/— thin MCP wrapper reusinganalyze_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
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.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).Render deploy friction — Python 3.14 default (Render ignores
runtime.txt), gunicornModuleNotFoundErrorand unsupported--factory, free-tier cold starts causingoperation_timeout. We pinned Python 3.11 via.python-version+PYTHON_VERSION, and simplified topython -m app.mainwith threaded Flask.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 TABLEin 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_sqlinto CI or Cursor rules so the same engine reviews SQL before it lands in a PR thread.

Log in or sign up for Devpost to join the conversation.