Last week a build broke and fixed itself. No one watched a console, no one triaged. The failure filed its own ticket and an agent shipped the PR. The most-liked line from that post was simple: build fails → bug appears → agent fixes.
That pattern isn’t specific to builds. It’s the queue.
At Shipeasy, every production signal that matters becomes a ticket that an agent can act on — no human triage in between.
Not a log line. Not a Slack message that scrolls past. A first-class work item in an ops queue that fans out to GitHub issue and Slack and is eligible for auto-fix.
The problem — failures had no queue
Builds broke, webhooks failed, checks flaked. The signal existed, but it had nowhere to go that an agent could pick up. Someone had to notice it in a console, copy the logs, write a bug, tag it, and hope the right person saw it hours later.
The cost wasn’t the failure. It was the queue that didn’t exist.
The fix — one queue for everything that can fail
Every external event hits one shape: a ticket. Same fields, same routing, same agent lifecycle.
A Cloud Build failure. A flaky E2E run. A customer report from the widget. A cron that missed its window. All of them land as type: "bug" via the admin API — title, repro steps, actual vs expected, priority, tags — and then the platform does the same thing every time: opens a GitHub issue, pings the right Slack channel, marks it eligible for an AI agent to investigate and open a PR.
Build on main breaks → ticket → agent PR is just one instance of the pattern. The pattern is what matters.
Design — boring on purpose
- Source → already on a bus (Pub/Sub, webhook, schedule) — no new infra
- Push or POST to
/webhooks/<source>— verify token, decode, dedupe by delivery ID - Client →
POST /api/admin/opswithtype: "bug"— title, stepsToReproduce, actualResult, expectedResult, priority, tags - Shipeasy ops queue → fans out to GitHub issue + Slack → agent investigates → PR
What makes this cheap is what we didn’t build: we use the delivery mechanism the cloud already provides and the service we already run. The glue is forty lines.
Implementation — one thin client
Same shape every time:
ShipeasyOps::Client.new.file_bug(
title: "Cloud Build FAILURE on #{branch} — #{sha}",
steps_to_reproduce: "Trigger \"#{trigger}\" reported FAILURE.",
actual_result: "Logs: #{log_url}\n\n#{failure_detail}",
expected_result: "Build completes and deploys.",
priority: "high",
tags: %w[cloud-build ci]
)
The method is a typed wrapper over one HTTP call:
def file_bug(title:, steps_to_reproduce:, actual_result:, expected_result:, priority:, tags:)
post("/api/admin/ops", {
type: "bug",
title: title,
stepsToReproduce: steps_to_reproduce,
actualResult: actual_result,
expectedResult: expected_result,
priority: priority,
tags: tags,
})
end
def post(path, body)
req = Net::HTTP::Post.new(URI("#{BASE_URL}#{path}"))
req["Authorization"] = "Bearer #{@admin_key}"
req["X-Project-Id"] = @project_id
req["Content-Type"] = "application/json"
req.body = body.compact.to_json
res = Net::HTTP.start(req.uri.host, req.uri.port, use_ssl: true) { |h| h.request(req) }
JSON.parse(res.body)
end
Dedupe at the edge (write-if-not-exists on the delivery ID) so at-least-once delivery doesn’t create duplicate tickets.
Ways this could have gone
- Log the error via standard flag evaluation: good for exceptions, not a tracked work item with priority and repro.
- Public ticket path for in-app feedback: zero-auth, built for "report a problem" widgets — too heavy for an internal signal.
- File a proper bug via admin API (chosen): real work item, prioritized, tagged, routed, agent-ready.
Payoff — from red to green without paging anyone
A typical failure now travels from a bus to a PR without waking anyone. The team sees a PR that already exists and merges it. The person who merges it doesn’t need to know the failure mode.
The rule we use before building new plumbing: if the event is already on a bus you can subscribe to, and something you already run can catch it, file a ticket and let the ops queue do the rest.
Built on Shipeasy — ops queue that delegates to agents. Supporting infra: flags, kill switches, dynamic configs. Core loop lives in the queue, not the flag. Free tier, no card required; Team at $49/seat/mo removes limits.
→ shipeasy.ai · → docs.shipeasy.ai · → github.com/shipeasy-ai/shipeasy
Top comments (0)