Snowflake Cortex Code CLI Meets Slack
Cortex Code CLI is Snowflake's AI coding agent that runs in your terminal. It can write code, execute SQL and DDL scripts, create and deploy data pipelines, generate dashboards, reads & write files, manages git repos and more.
Cortex Code CLI also has a hook system and skill framework that make it extensible so you can build sidecar apps that plug into its lifecycle. I wanted to explore this extensibility by building a Slack bridge: get DM notifications about what the agent is doing, and reply with instructions to steer it, all from your phone. The whole conversation happens in Slack while the agent keeps working in the terminal. No servers, no databases, no cloud infra. Just a Slack bot, some JSON files, and a shell wrapper.
What It Actually Does
It supports two interaction patterns:
- Free-text conversations: You type a message in the Slack DM. It lands in a session-specific inbox file. The agent's cron job picks it up on the next cycle and treats it as user input. This is the core of the bridge — you can ask questions (“what's the row count on that table?”), give instructions (“skip that step and move to the next one”), make decisions (“use approach #2”), or redirect the work entirely (“actually, focus on the API layer first”). The agent responds back via Slack.
- Notifications: The agent sends you status updates as Slack DMs. “Feature engineering done, 5 tables created.” “Model training complete, 3 models evaluated.” “Blog post committed and pushed.” You see them on your phone and know what's happening without being at the terminal.
Note: This bridge works best with “bypass safeguards” enabled in Cortex Code CLI, which lets the agent execute tools without pausing for terminal confirmations. That's what makes the remote workflow possible; if the agent stopped to ask for permission at the terminal, you'd have to be sitting there anyway.
But understand the tradeoff: bypass safeguards means the agent can execute bash commands, write files, run SQL, and push to git without asking first. You're trusting the agent to do the right thing. The Slack bridge gives you visibility (notifications) and steering (free-text replies), but it doesn't replace the confirmation gate — it replaces the terminal. Use it on repos and environments where you're comfortable with that level of autonomy.
Key Design Decisions
- Socket Mode: No public URLs needed. The bot connects outbound to Slack's WebSocket API. Works behind firewalls, no ngrok required.
-
File-based inbox: Each Cortex Code CLI session gets its own
inbox_{session_id}.json. The bridge bot writes to it; the CLI polls it via cron. Dead simple, zero dependencies. - Metadata routing: Every outbound Slack message includes session metadata. When you reply, Slack sends that metadata back. The bridge uses it to write to the correct session's inbox. Multiple sessions, no crosstalk.
- Sidecar process: The bridge bot runs as a background process, started automatically by a SessionStart hook. It's not embedded in Cortex Code CLI ; it's a separate Python process that communicates via the filesystem.
The Code
The project has four main files.
1) config.py: Configuration and Session Management
It handles paths, tokens, and multi-session routing.
BRIDGE_DIR = Path.home() / ".cortex-slack-bridge"
INBOX_FILE = BRIDGE_DIR / "inbox.json"
PID_FILE = BRIDGE_DIR / "bridge.pid"
Token lookup follows a priority chain: environment variable > macOS Keychain > config file. Env vars (SLACK_BRIDGE_APP_TOKEN, SLACK_BRIDGE_BOT_TOKEN) are checked first, then Keychain via the security CLI, then ~/.cortex-slack-bridge/config.json as a fallback. The Keychain path means tokens never sit in plain text on disk:
KEYCHAIN_SERVICE = "coco-slack-bridge"
def keychain_get(key: str) -> str | None:
try:
result = subprocess.run(
["security", "find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", key, "-w"],
capture_output=True, text=True, timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
return None
Zero new dependencies; just the security CLI that ships with macOS. Session management is minimal: get_session_inbox() returns the inbox path for a session ID, and set_active_session() / get_active_session() track which session the bridge should route messages to when there's no metadata to go on.
2) bridge.py: The Socket Mode Bot
This is the long-running process. It connects to Slack via Socket Mode so when you type in the Slack DM, it captures the text and writes it to the active session's inbox:
@app.event("message")
def handle_dm(event, say):
user = event.get("user")
if subtype or user != target_user:
return
_append_inbox({
"type": "reply",
"text": event.get("text", ""),
# ...
})
say("Message sent to CoCo CLI. Awaiting response…")
Both bridge.py and notify.py append every message to ~/.cortex-slack-bridge/history.jsonl which acts as a permanent audit trail. Inbound DMs, outbound notifications, consumed confirmations — everything gets logged with direction and timestamp.
def _log_history(entry: dict, direction: str):
try:
record = {**entry, "direction": direction, "logged_at": time.time()}
with open(HISTORY_FILE, "a") as f:
f.write(json.dumps(record) + "\n")
except Exception:
pass # history logging done not break core functionality
Three direction values: inbound (user DMs and button clicks), outbound (agent notifications and confirmation requests), consumed (confirmation responses popped from the inbox).
3) notify.py: Sending Messages
This is the outbound side. The main function is send_message() — it sends a plain DM or a color-coded message (blue for status, green for success, yellow for warnings, red for errors). Every message includes session metadata so replies route back correctly:
metadata = {
"event_type": "cortex_bridge",
"event_payload": {"session_id": sid},
}
There's also a send_confirmation() function that sends Approve/Deny buttons and polls the inbox for a response. It's used by coco-bridge confirm for scripted gate checks.
4) bin/coco-bridge: The Shell Wrapper
A bash script that makes everything easy to use from Cortex Code CLI's skill system:
coco-bridge start # Start the bridge bot
coco-bridge stop # Stop it
coco-bridge status # Check if running
coco-bridge send "message" # Send a DM
coco-bridge send "msg" - type success # Color-coded
coco-bridge confirm "question" # Approve/Deny buttons
coco-bridge inbox # Read inbox contents
coco-bridge history # Last 20 messages (audit log)
coco-bridge history 50 # Last N messages
coco-bridge setup-keychain # Store tokens in macOS Keychain
coco-bridge clear-keychain # Remove tokens from Keychain
coco-bridge logs # Tail the log file
It auto-detects the project's virtualenv, manages the PID file, and dispatches to the Python modules.
Why This Works
Most AI coding agents don't expose lifecycle hooks as in programmatic entry points where your own code runs at specific moments in the agent's execution. Cortex Code CLI does! It has a hook system that fires shell commands at session start, after every tool call, on user input, and at session end. Combined with a skill framework that lets you teach the agent new behaviors via markdown files, you get a real extensibility contract. This Slack bridge exists entirely because of that, and it's worth understanding the two mechanisms that make it work.
The Hook System
Cortex Code CLI has a lifecycle hook system defined in ~/.snowflake/cortex/hooks.json. You can register shell commands that fire at specific points:
- SessionStart: runs when a new session begins (before the agent does anything)
- PostToolUse: fires after every tool call (bash, file write, SQL execution, etc.)
- UserPromptSubmit: fires when the user sends a message
- Stop: fires when the session ends
- SubagentStop: fires when a background agent completes
Each hook is a shell command with a configurable timeout. The stdout from SessionStart hooks gets passed to the agent as context, which is how the bridge tells Cortex Code CLI “hey, I'm running, ask the user if they want Slack enabled.”
This is a big deal. It means you can build sidecar applications that plug into Cortex Code CLI's lifecycle without modifying the CLI itself.
The Slack bridge uses SessionStart to auto-launch. But you could just as easily build:
- A PostToolUse hook that logs every tool call to a database (audit trail)
- A UserPromptSubmit hook that captures conversation history (flight recorder)
- A Stop hook that sends a session summary to Slack or email
Setting It Up
Prerequisites
- Python 3.10+
- A Slack workspace where you can install apps
- Cortex Code CLI
Create the Slack App
- Go to api.slack.com/apps and create a new app
- Under Socket Mode, enable it and create an App-Level Token with
connections:writescope. Copy thexapp-…token. - Under OAuth & Permissions, add these Bot Token Scopes:
chat:writeto send DMs,im:historyto read DM history,im:readto view DM channels,im:writeto open DM channels - Under Event Subscriptions, enable events and subscribe to the
message.imbot event - Under Interactivity & Shortcuts, enable interactivity (no URL needed for Socket Mode)
- Install the app to your workspace. Copy the Bot User OAuth Token (
xoxb-…). - Find your Slack User ID: click your profile picture in Slack, click the three dots, “Copy member ID”
NOTE: the app-level token, bot user oauth token and your Slack member ID will be used later under Configure Tokens section.
Install the Bridge
git clone https://github.com/iamontheinet/cortex-code-cli-slack-bridge.git \
~/Apps/cortex-code-cli-slack-bridge
cd ~/Apps/cortex-code-cli-slack-bridge
python3 -m venv .venv
.venv/bin/pip install -e .
Install Cortex Code CLI Skill
Cortex Code CLI has a skill system that lets you teach the agent new behaviors via markdown files. The repo includes the Cortex Code CLI skill file that teaches the agent how to manage the bridge. For example, when to poll, how to format messages, how to activate and deactivate.
Copy it to your skills directory:
mkdir -p ~/.snowflake/cortex/skills/slack-bridge
cp skill/SKILL.md ~/.snowflake/cortex/skills/slack-bridge/SKILL.md
The Slack bridge skill defines:
- Trigger phrases: “slack on”, “enable slack”, “/slack”, etc.
- Activation flow: what to do when the user opts in
- Message routing: how to handle inbox entries, when to send notifications
- Deactivation: how “slack off” tears down the polling
When you say “slack on”, the skill instructs the agent to:
- Create a session-scoped cron job that polls the inbox every minute
- Send an activation message to Slack
- Start routing questions and status updates through Slack instead of the terminal
The cron pattern here is worth calling out. Cortex Code CLI's cron_create tool lets you schedule prompts that fire on a schedule — and they're session-scoped, meaning they die when the session ends. The bridge uses this as a heartbeat: every minute, a “Slack inbox check” prompt fires. The skill tells the agent to read the inbox file, and if it's empty, sleep 30 seconds and check again. Two checks per cron fire gives you ~30-second worst-case latency without doubling the cron frequency or adding a file watcher. It's a cheap way to give an agent a polling loop — just a cron job and a JSON file. The skill also handles “slack off” to disable the bridge mid-session.
Configure Tokens
The recommended approach stores tokens in macOS Keychain:
coco-bridge setup-keychain
# Prompts for app token, bot token, and user ID
# Stores them in Keychain under the "coco-slack-bridge" service
If you already have a config.json, setup-keychain reads from it, migrates to Keychain, and offers to delete the file.
Alternatively, use a config file or env vars:
# Config file approach
mkdir -p ~/.cortex-slack-bridge
cp config.json.example ~/.cortex-slack-bridge/config.json
# Edit with your actual tokens
# Or env vars
export SLACK_BRIDGE_APP_TOKEN="xapp-1-A0…"
export SLACK_BRIDGE_BOT_TOKEN="xoxb-…"
export SLACK_BRIDGE_USER_ID="U02M…"
Start and Test
# Start the bridge
bin/coco-bridge start
# Send a test message
bin/coco-bridge send "Hello from the bridge!"
# Test a reply - type something back in the Slack DM, then check:
bin/coco-bridge inbox
Wire Up the SessionStart Hook
Add this to ~/.snowflake/cortex/hooks.json so the bridge auto-starts with every Cortex Code CLI session:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "/Users/YOUR_USERNAME/.cortex-slack-bridge/start-hook.sh",
"timeout": 10,
"enabled": true
}
]
}
]
}
}
Copy the start hook script:
cp demo-start-hook.sh ~/.cortex-slack-bridge/start-hook.sh
chmod +x ~/.cortex-slack-bridge/start-hook.sh
The Beauty of Extensibility
The whole project is ~300 lines of Python plus a shell wrapper. No servers, no databases, no cloud infra. It extends Cortex Code CLI with a mobile-friendly communication layer so you can kick off a task, walk away, get updates, reply with instructions, all from Slack.
But the bigger takeaway isn't the Slack bridge itself, it's that Cortex Code CLI is built to be extended this way. Hooks give you lifecycle access. Skills give you behavioral control. The filesystem gives you a dead-simple IPC layer. This bridge is one example; the pattern works for any sidecar you can imagine: audit loggers, custom approval gates, integration bridges to other tools.
Stay Connected
Thank you for your time. I hope you found this blog both educational and inspiring. The code can be found here.
Clone it, plug in your Slack tokens, and build something on top of it. The Cortex Code CLI is waiting for your hooks. If you're experimenting with this workflow or planning to, I'd love to hear what you're building.
Connect with me on LinkedIn for more demos as well as AI product & feature updates.
Top comments (0)