Connect Sentry and Linear to Slack
Verified August 11, 2026
Sentry knows what broke. Linear knows what you are doing about it. Nothing joins them, so someone reads the alert, searches Linear, finds nothing, and files the issue by hand.
This is one Slack bot that does the join. Ask it about an error and it reads the Sentry issue, searches Linear for a matching one, and files it with the stack trace and the release if there is none.
Both services have a Slack app already. Neither can do this, because neither can see the other.
What you need
- A Slack workspace where you can install apps
- A Sentry account and a Linear account
- An OpenRouter account
Install the CLI
npm i -g @substructure.ai/cliWrite the config
Save this as subs.toml.
name = "triage-bot"
[llm.openrouter]
type = "openrouter"
[mcp.sentry]
url = "https://mcp.sentry.dev/mcp"
[mcp.linear]
url = "https://mcp.linear.app/mcp"
[agent.triage]
llm = "openrouter"
model = "z-ai/glm-5.2"
system = """
You are the triage assistant. When asked about an error, read it in Sentry, then
search Linear for an existing issue about it. File one only if none exists, and
include the stack trace and the affected release. Link the Linear issue in your
reply.
"""
mcp = [
{ id = "sentry", tools = { read_only = true } },
{ id = "linear", tools = { include = ["*issue*", "*search*", "*create*"] } },
]
[agent.triage.slack]
name = "Triage"
[remote]
url = "https://api.substructure.ai"[agent.triage] declares the agent: its model, its prompt, and the connections it can reach. The Slack bot is how you talk to it.
The file holds two server URLs and the agent that uses both. It holds no tokens.
[remote] is the deployment the CLI talks to. subs apply writes the org and the project back under it.
Create the project
subs applyThis creates the project from the file and writes the project id back into it. If you are not signed in yet, the command opens your browser first. Run subs apply again after any edit to the file.
Add your LLM key
subs auth llm.openrouterThe agent's model runs through OpenRouter, so it needs an OpenRouter API key. Create one at openrouter.ai/keys if you do not have one. The command stores the key with your deployment. It never goes in the config file.
Authorize Sentry and Linear
A credential belongs to a connection id, so each one is authorized on its own.
subs auth mcp.sentry
subs auth mcp.linearEach command opens that service's consent page. Approve it and the credential goes to your deployment.
Run subs list to check both connections are authorized. A declared connection with no credential reaches nothing, and the bot will answer from one service while silently failing at the other.
Set up the Slack app
subs auth agent.triage.slackThe command prints an app manifest. Go to api.slack.com/apps, choose Create New App, then From a manifest, and paste it in. Create the app and install it to your workspace.
Slack then shows you two values. Paste them back when the command asks.
- The Bot User OAuth Token, under OAuth & Permissions.
- The Signing Secret, under Basic Information.
The bot is live once both are in.
Ask it something
Invite the bot to a channel and mention it.
- "what is breaking in checkout, and is it tracked?"
- "triage the top error from last night"
- "file the null plan error if nobody has"
Each tool call shows in the thread as a task card. A single answer usually spans several: one or two into Sentry, one search in Linear, and a create if the search came back empty. Open a card to see the arguments and the result.
Read one, write the other
The filter belongs to the agent, not to the connection. That is what makes the asymmetry possible.
mcp = [
{ id = "sentry", tools = { read_only = true } },
{ id = "linear", tools = { include = ["*issue*", "*search*", "*create*"] } },
]Sentry is read-only, so the bot can never resolve or assign an error there by mistake. Linear takes an include list rather than read_only, because filing is the point.
The filter is also what keeps the bot working. A model picks worse as the tool list grows, and worst between tools that look alike, which is exactly what two full servers give it. Naming an id on its own takes every tool it offers.
Because the filter is per agent, one Sentry connection can serve two agents that see different tools. A read-only agent in a public channel and a wider one in your own DMs share a single credential.
Tool names carry their connection
Both servers offer a search. Tools arrive prefixed with the connection id, so they stay distinct.
sentry__search_issues
linear__search_issuesThe system prompt is where you say which to reach for first. Without it a model will sometimes search Linear for the error text and answer that nothing is wrong.
If it does not answer
- One connection not authorized. Run
subs list. A bot with Sentry but not Linear reads the error and files nothing. - The Slack app has no values yet. Run
subs list. An app with nothing set never replies. - Bot not invited. Invite it to the channel before you mention it.
- It answers but never files. Check the Linear filter is not
read_only, and that the include list covers the create tool. - Tools missing. The engine fetches the tool list once per session. Start a new thread after you change a filter.
- Anything else. Run
subs doctor.
Add a third service
Declare it and name it on the agent. GitHub turns triage into the full chain: the error, the pull request that introduced it, and the issue that tracks it.
[mcp.github]
url = "https://api.githubcopilot.com/mcp/"
auth = "token"
[agent.triage]
mcp = [
{ id = "sentry", tools = { read_only = true } },
{ id = "linear", tools = { include = ["*issue*", "*search*", "*create*"] } },
{ id = "github", tools = { include = ["*pull_request*", "*commit*"] } },
]GitHub takes a static token rather than OAuth, so subs auth mcp.github reads a token rather than opening a consent page. Three servers is where filtering stops being optional.
Next steps
- Connect a Slack bot to Sentry or to Linear, one service at a time.
- Run the bot on an open model: prices, context, and what a thread costs.
- How MCP connectors work: auth, tool fetching, and filters.
- Per-channel agents: point one channel at this bot and another somewhere else.
- Pricing: one flat price a month, and no per-token charges from us.