I built an oncall triage bot that polls Slack for CI/CD alerts, matches them against a knowledge base of past failures, generates a diagnosis, and posts it back to the thread. It works great. One problem: it runs as a long-running Claude Code session on my MacBook. When my laptop sleeps, the bot sleeps.
Now I want my teammates to have it during their oncall rotations. This post is me thinking through how.
What the thing actually does
The loop is simple:
- Poll two Slack channels (
#ci-cd-alerts,#support-ci-cd) for new bot messages - Parse the alert (failed job, run ID, error snippet)
- Match against a historical knowledge base (JSONL files of past incidents with confirmed root causes)
- Generate a triage diagnosis using Claude’s API
- Post a formatted reply to the Slack thread
Steps 1-3 are deterministic Python. Step 4 is the AI part. Step 5 is a curl call.
The audience is ~8 people on a DevEx squad. Low stakes, high quality-of-life impact. Nobody dies if the bot is down for an hour. But when it’s running, the oncall person lands on an alert thread and the diagnosis is already there.
The constraint that matters
The polling and KB matching don’t need Claude Code. You could extract them into a standalone Python script with the Anthropic API for the diagnosis step. That’s the clean path.
But the current version benefits from running inside a Claude Code session because the diagnosis step isn’t just an API call. It’s Claude reasoning over the KB, the error logs, the historical patterns, and sometimes pulling additional context (like checking if a GH Actions run has related failures). That’s hard to replicate with a single messages.create() call and a system prompt.
So the design question is really two questions:
- Where does this run?
- How much of the Claude Code session do you actually need?
Option 1: GitHub Actions cron
Run the polling script on a schedule. Every 5 minutes, a GH Actions workflow fires, checks Slack for new alerts since the last run, triages them, posts replies.
What’s good: Zero infra to manage. The team already lives in GitHub. Secrets (Slack token, Anthropic key) go in repo settings. Logs are in the Actions UI. If it breaks, the CI team of all people knows how to debug a workflow.
What’s tricky: GH Actions has a minimum cron interval of 5 minutes, and in practice it drifts. You might see 7-10 minute gaps. For oncall triage that’s probably fine. Nobody needs sub-minute response times on a CI alert diagnosis.
The bigger issue: the diagnosis step. If you’re calling the Anthropic API directly, you need to pack all the context (KB entries, error logs, historical patterns) into the prompt yourself. No persistent session state. Every invocation starts cold. You can mitigate this with a good system prompt and relevant KB entries retrieved via embedding search or keyword matching, but it’s a different architecture than “Claude Code session that remembers the KB.”
Verdict: Best starting point. Lowest friction. The cold-start problem is real but solvable with a well-designed prompt + retrieval step.
Option 2: Shared VM or Codespace
Spin up a persistent VM (or a GitHub Codespace) where the bot runs as a long-lived process. Teammates SSH in to check on it, restart it, update the KB.
What’s good: Uptime doesn’t depend on anyone’s laptop. The process can maintain state between polls. You could even keep a Claude Code session running if you wanted, though that’s fragile.
What’s tricky: Someone has to maintain the VM. Codespaces have idle timeouts (you can extend them, but it’s another thing to configure). SSH access means credential management. And the Claude Code session approach is fundamentally designed for interactive use. Running it headless in a tmux session on a VM works until it doesn’t.
Verdict: Good if you need persistent state or if the GH Actions cold-start is unacceptable. More ops overhead than a cron job.
Option 3: Container on ARC runners
Our team already manages ARC (Actions Runner Controller) infrastructure for CI runners. Deploy the bot as a container on the same Kubernetes cluster.
What’s good: Infra already exists. The team already knows how to operate it. Container gives you reproducible environment. You get real process supervision (restart on crash), logging, resource limits.
What’s tricky: It’s the most “production” option for what is fundamentally a squad tool. You’re writing a Dockerfile, a Helm chart (or at least a k8s manifest), and a deployment pipeline for something that started as a script on a laptop. That’s a lot of ceremony for 8 users.
Also, ARC runners are designed for ephemeral job execution, not long-running services. You’d be deploying a sidecar container alongside the runner infrastructure, not using it as a runner. Doable, but you’re bending the tool.
Verdict: Right choice if this grows beyond the squad. Overkill for now.
Option 4: launchd + wake schedule (Mac only)
Keep it on the Mac. Use launchd to run the script on a schedule, configure a wake schedule so the machine wakes from sleep to run it.
What’s good: Nothing to deploy. Nothing to migrate. It just keeps working the way it already works.
What’s tricky: It only works when the Mac is powered on and connected to the network. It doesn’t solve the sharing problem at all. When someone else is oncall, they don’t have access to my machine.
Verdict: Not a real option unless you only care about your own oncall shifts.
The pattern underneath
This is a common trajectory for internal tools:
- You build something that solves your problem
- It works well enough that other people want it
- Now you need to figure out deployment, auth, state management, and monitoring for something you built in an afternoon
The instinct is to jump to option 3 (containerize it, deploy it properly). But for a squad tool with 8 users, that’s like buying a forklift to move a couch. The GH Actions cron gets you 80% of the value with 20% of the work. You lose persistent session state, but you gain zero-maintenance deployment and a familiar execution environment.
The harder design decision is what to do about the AI reasoning step. A cold Anthropic API call with a well-crafted prompt and good retrieval is probably 70-80% as good as a persistent Claude Code session. For oncall triage where the alternative is “engineer reads the logs themselves,” that’s more than good enough.
What I’m actually going to do
- Extract the polling + KB matching into a standalone Python script
- Replace the Claude Code reasoning with a direct Anthropic API call, with a system prompt that includes the KB matching results and historical context
- Run it as a GH Actions cron job, every 5 minutes
- If the diagnosis quality drops noticeably, invest in better retrieval (embeddings over the KB, maybe a vector store)
- If the team outgrows the cron job, move to a container
Step 2 is where the actual work is. The rest is plumbing.
Takeaway
When you’re deciding how to ship a local tool to your team, start by asking what the tool actually needs at runtime. Persistent state? Real-time responsiveness? Access to local files? If the answer is “not really,” then the simplest deployment wins. A cron job that runs a script is boring, but boring is maintainable.
The AI-powered part makes this feel special, but from a deployment perspective, it’s still just a script that reads from one API and writes to another. Ship it like one.