Buildkite Linear Issue Pipeline Example
This example demonstrates an AI-powered issue analysis pipeline built with Buildkite dynamic pipelines and Claude Code. When a Linear issue gets a specific label, a Buildkite pipeline kicks off an AI agent to analyse the issue and start working on it.
How it works
- A Linear issue is created or updated with the
buildkite-analyzelabel - Linear sends a webhook to Buildkite, which starts a build
- The first step evaluates the webhook payload — if it’s not a create/update action or the label doesn’t match, the build exits early
- A TypeScript handler reads the Linear webhook payload, extracts the issue ID, title, and description
- The handler uses the Buildkite SDK to dynamically generate an analysis step and uploads it with
buildkite-agent pipeline upload - That step launches Claude Code in a Docker container with access to the codebase, Linear (via the Linear CLI), and GitHub (via
ghCLI) to analyse and work on the issue
The handler pattern
The core of the handler is short — read the webhook payload from build metadata, evaluate whether to act, and generate a step with the Buildkite SDK:
// 1. Read the webhook payload that Buildkite stored as build metadata
const payload = JSON.parse(
execSync("buildkite-agent meta-data get buildkite:webhook").toString(),
);
// 2. Evaluate the condition — create/update action with a matching label?
const labels = (payload.data.labels ?? []).map((l) => l.name);
if (!["create", "update"].includes(payload.action) || !labels.includes(process.env.TRIGGER_ON_LABEL)) {
process.exit(0);
}
// 3. Generate a step with the Buildkite SDK and pipe it into `pipeline upload`
const pipeline = new Pipeline();
pipeline.addStep({ label: ":linear: Analyse the issue", command: "scripts/claude.sh" });
execSync("buildkite-agent pipeline upload", { input: pipeline.toYAML() });
The real handler extracts the issue ID, title, and description between steps 2 and 3 so Claude has them on process.env — see scripts/handler.ts.
The key Buildkite features at play:
buildkite-agent pipeline upload— adding steps to a running build based on runtime conditionsbuildkite-agent meta-data— reading webhook payloads stored as build metadata@buildkite/buildkite-sdk— programmatically generating pipeline YAML in TypeScript- Buildkite webhooks — triggering builds from external events (Linear, not just GitHub)
- Buildkite Hosted Models — proxying LLM requests through Buildkite’s model provider endpoint
What’s interesting about this?
This example shows that dynamic pipelines aren’t limited to GitHub workflows. Any webhook source — Linear, Jira, PagerDuty, your own services — can trigger a Buildkite build that evaluates the payload and dynamically decides what to do. The handler pattern (read metadata → evaluate conditions → generate and upload steps) works the same regardless of where the webhook comes from.
Setup
To run this yourself, you’ll need:
- A Buildkite account
- A Linear workspace with webhook support
- A Linear API token
- An Anthropic API key (or use Buildkite Hosted Models)
- Docker installed on your Buildkite agent
- Fork this repo
- Create a Buildkite pipeline pointing to your fork with webhook support enabled
- In Linear, create a webhook pointing to your Buildkite pipeline’s webhook URL, sending
Issueevents - Set up the required secrets:
GITHUB_TOKEN,BUILDKITE_API_TOKEN, andLINEAR_API_TOKEN - Add the
buildkite-analyzelabel to any Linear issue
Credits
Originally built by Grant Colegate and Christian Nunciato as a demo for AWS re:Invent.
License
See LICENSE (MIT)



