Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:00:26 +08:00
commit 11bee70e53
13 changed files with 2402 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "ci-cd-tools",
"description": "CI/CD pipeline tools and integrations",
"version": "1.0.0",
"author": {
"name": "Josh Nichols",
"email": "josh@technicalpickles.com"
},
"skills": [
"./skills"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# ci-cd-tools
CI/CD pipeline tools and integrations

81
plugin.lock.json Normal file
View File

@@ -0,0 +1,81 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:technicalpickles/pickled-claude-plugins:plugins/ci-cd-tools",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "fd3fb228885b46b8133e352d21d3914dd06f9554",
"treeHash": "07ab8cafe621666a03bb940dae73f249d824114affbbb46c4c8b202f157bb1c7",
"generatedAt": "2025-11-28T10:28:36.223024Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "ci-cd-tools",
"description": "CI/CD pipeline tools and integrations",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "6b5fe70c92bc2ed5978a9989de443c5a1309844ec9be30c2b22f43ea6d3fe3a7"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "da8e209b5b52810ee061a6fb16af1a02a181c26e7f8bb4c1b499f0d94c1ac641"
},
{
"path": "skills/buildkite-status/SKILL.md",
"sha256": "733066694d44fe9128fca348deb283bb69e249ec6efef1f36159fa70bbe5507a"
},
{
"path": "skills/buildkite-status/references/url-parsing.md",
"sha256": "4e16daf6eae951a64673d27b2231d3334274aeeeaf7e51084d633bdd610ccd85"
},
{
"path": "skills/buildkite-status/references/troubleshooting.md",
"sha256": "49a32ad4944b551020e0f985493656944004193423693e95c977a2787c8bc5f6"
},
{
"path": "skills/buildkite-status/references/buildkite-states.md",
"sha256": "05c3bd32e5552301310cf86a7efc75f73d1a9f33f854d0d84f953bac820b2bd4"
},
{
"path": "skills/buildkite-status/references/annotation-patterns.md",
"sha256": "a59d03b7fabfc3dc382547134604741f56e52edb8487b209ca8649b46ff02019"
},
{
"path": "skills/buildkite-status/references/tool-capabilities.md",
"sha256": "c590a1763df6a86d71759a7b6b6bacde00468502a1db86b0496c7c27be88d86b"
},
{
"path": "skills/buildkite-status/scripts/parse-buildkite-url.js",
"sha256": "11fba63d54ba7363cfe464bc6bfdd0fb9966f069a9f41aac6ec8c025b39a1fa4"
},
{
"path": "skills/buildkite-status/scripts/find-commit-builds.js",
"sha256": "6a18b4a568e9c2b42b11130ebdc73af5b153772a91346aae44656a6e644ee6b4"
},
{
"path": "skills/buildkite-status/scripts/wait-for-build.js",
"sha256": "8baae518c2aa73b3297e39f5330cbcae5208515285ee7627bf09178b94660d42"
},
{
"path": "skills/buildkite-status/scripts/get-build-logs.js",
"sha256": "e1a96e3dba1eddb0ef982fba9bf3de09aab01d73c2030f3ac2e29e73531c9d78"
}
],
"dirSha256": "07ab8cafe621666a03bb940dae73f249d824114affbbb46c4c8b202f157bb1c7"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,592 @@
---
name: buildkite-status
description: Use when checking Buildkite CI status for PRs, branches, or builds - provides workflows for monitoring build status, investigating failures, and handling post-push scenarios with progressive detail disclosure. Use when tempted to use GitHub tools instead of Buildkite-native tools, or when a Buildkite tool fails and you want to fall back to familiar alternatives.
---
# Buildkite Status
## Overview
This skill provides workflows and tools for checking and monitoring Buildkite CI status. It focuses on **checking status and investigating failures** rather than creating or configuring pipelines. Use this skill when working with Buildkite builds, especially for PR workflows, post-push monitoring, and failure investigation.
## When to Use This Skill
Use this skill when:
- Checking CI status for the current branch or PR
- Investigating why a build failed
- Monitoring builds after a git push
- Waiting for builds to complete
- Checking build status across multiple repos/PRs
- Understanding what "broken" or other Buildkite states mean
## Tool Hierarchy and Selection
**CRITICAL**: Always use Buildkite-native tools. Never fall back to GitHub tools (`gh pr view`, GitHub API, etc.) - they only show summaries and lose critical information (annotations, logs, real-time updates, state distinctions).
Use tools in this priority order:
### Primary: MCP Tools (Always Use These First)
**Reliability**: Direct Buildkite API access, always available
**Capabilities**: All operations (list, get, wait, unblock)
**When**: Default choice for ALL workflows
Available MCP tools:
- `buildkite:get_build` - Get detailed build information
- `buildkite:list_builds` - List builds for a pipeline
- `buildkite:list_annotations` - Get annotations for a build
- `buildkite:get_pipeline` - Get pipeline configuration
- `buildkite:list_pipelines` - List all pipelines in an org
- **`buildkite:wait_for_build`** - Wait for a build to complete (PREFERRED for monitoring)
- **`buildkite:get_logs`** - Retrieve job logs (CRITICAL for debugging failures)
- `buildkite:get_logs_info` - Get log metadata
- `buildkite:list_artifacts` - List build artifacts
### Secondary: bktide CLI (Convenience)
**Purpose**: Human-readable terminal output
**Limitation**: External dependency, requires npm/npx
**When**: Interactive terminal work when MCP output is too verbose
**Critical Limitation**: bktide CANNOT retrieve job logs. It only displays build summaries and job lists. For log retrieval, always use MCP tools.
Common commands:
```bash
npx bktide pipelines <org> # List pipelines
npx bktide builds <org>/<pipeline> # List builds
npx bktide build <org>/<pipeline>#<build> # Get build details
npx bktide annotations <org>/<pipeline>#<build> # Show annotations
```
### Tertiary: Bundled Scripts (Helper Wrappers)
**Purpose**: Pre-built workflows combining multiple tool calls
**Limitation**: External dependencies (bktide, specific versions)
**When**: Convenience wrappers only - use MCP tools if scripts fail
This skill includes scripts for common workflows:
- **`scripts/wait-for-build.js`** - Background monitoring script that polls until build completion
- **`scripts/find-commit-builds.js`** - Find builds matching a specific commit SHA
### Tool Capability Matrix
Different tools have different capabilities. Understanding these limitations prevents wasted effort.
**Key Capabilities:**
| Capability | MCP Tools | bktide | Scripts |
| ----------------- | --------- | ------ | ------- |
| List builds | ✅ | ✅ | ✅ |
| Get build details | ✅ | ✅ | ✅ |
| Get annotations | ✅ | ✅ | ❌ |
| **Retrieve logs** | **✅** | **❌** | **✅** |
| Wait for build | ✅ | ❌ | ✅ |
| Unblock jobs | ✅ | ❌ | ❌ |
**Most Important**: Only MCP tools and scripts can retrieve job logs. bktide cannot.
For complete capability details and examples, see [references/tool-capabilities.md](references/tool-capabilities.md).
### When Tools Fail: Fallback Hierarchy
**If wait-for-build.js script fails:**
1. ✅ Use `buildkite:wait_for_build` MCP tool instead (preferred)
2. ✅ Use `buildkite:get_build` MCP tool in a polling loop
3. ❌ Do NOT fall back to `gh pr view` or GitHub tools
**If bktide fails:**
1. ✅ Use equivalent MCP tool
2. ❌ Do NOT fall back to GitHub tools
**If MCP tools fail:**
1. ✅ Check MCP server connection status
2. ✅ Restart MCP connection
3. ✅ Report the MCP failure to your human partner
4. ❌ Do NOT fall back to GitHub tools
**Critical**: One tool failing does NOT mean the entire skill is invalid. Move up the hierarchy, don't abandon Buildkite tools.
## Core Workflows
### 1. Investigating a Build from URL (Most Common)
When a user provides a Buildkite URL for a failing build, follow this workflow to investigate.
**Example URL formats:**
- Build URL: `https://buildkite.com/org/pipeline/builds/12345`
- Step URL: `https://buildkite.com/org/pipeline/builds/12345/steps/canvas?sid=019a5f...`
**Step 1: Extract build identifiers from URL**
Parse the URL to extract:
- Organization slug (e.g., "gusto")
- Pipeline slug (e.g., "payroll-building-blocks")
- Build number (e.g., "12345")
Ignore the `sid` query parameter - it's a step ID, not needed for initial investigation.
**Step 2: Get build overview**
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
detail_level: 'summary',
});
```
Check the overall build state: `passed`, `failed`, `running`, `blocked`, `canceled`.
**Step 3: Identify failed jobs**
If build state is `failed`, get detailed job information:
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
detail_level: 'detailed',
job_state: 'failed',
});
```
This returns only jobs with `state: "failed"` (not "broken" - see state reference).
**Step 4: Retrieve logs for failed jobs**
For each failed job, extract its `uuid` field and retrieve logs. See "Retrieving Job Logs" workflow below for detailed instructions.
**Step 5: Analyze error output**
Look for:
- Stack traces
- Test failure messages
- Exit codes and error messages
- File paths and line numbers
**Step 6: Help reproduce locally**
Based on the error, suggest:
- Which tests to run locally
- Environment setup needed
- Commands to reproduce the failure
---
### 2. Retrieving Job Logs
**CRITICAL**: This is the most important capability. Without logs, you cannot debug failures.
Once you've identified a failed job, retrieve its logs to see the actual error.
**Prerequisites:**
- Organization slug
- Pipeline slug
- Build number
- Job UUID (from build details)
**Important**: Job UUIDs ≠ Step IDs. URLs contain step IDs (`sid=019a5f...`), but MCP tools need job UUIDs from the build details response.
**Step 1: Get the job UUID**
If you have a job label (e.g., "ste rspec"), use `get_build` with `detail_level: "detailed"`:
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '29627',
detail_level: 'detailed',
job_state: 'failed',
});
```
In the response, find the job by matching the `label` field. Extract its `uuid` field (format: `019a5f20-2d30-4c67-9edd-...`).
**Step 2: Retrieve logs using the job UUID**
Use the MCP tool to get logs:
```javascript
mcp__MCPProxy__call_tool('buildkite:get_logs', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '29627',
job_id: '<job-uuid>',
});
```
The response contains the log output from the job execution.
**Common Issues:**
- **"job not found" error**: You likely provided a step ID instead of a job UUID. Step IDs come from URLs (`sid=019a5f...`). Job UUIDs come from `get_build` API responses. Solution: Call `get_build` with `detail_level: "detailed"` to find the correct job UUID.
- **Empty logs**: The job may not have started yet, or logs may not be available yet. Check the job's `state` field first - it should be in a terminal state (`passed`, `failed`, `canceled`).
- **Multiple jobs with same label**: Some pipelines parallelize jobs with the same label (e.g., "rspec (1/10)", "rspec (2/10)"). Filter by the full label string to find the specific failed job.
**Fallback Strategy:**
If MCP tools fail (e.g., connection issues, permissions), you can:
1. Construct the log URL manually and view in browser:
```
https://buildkite.com/{org}/{pipeline}/builds/{build}/jobs/{job-uuid}
```
2. Use the bundled script (if available):
```bash
~/.claude/skills/buildkite-status/scripts/get-build-logs.js <org> <pipeline> <build> <job-uuid>
```
**Why bktide Cannot Help:**
The bktide CLI does NOT have a logs command. It can show build summaries and job lists, but cannot retrieve log content. Always use MCP tools for log retrieval.
See [references/tool-capabilities.md](references/tool-capabilities.md) for complete tool capability matrix.
---
### 3. Checking Current Branch/PR Status
This is the most common workflow when working on a branch:
**Step 1: Identify the pipeline and branch**
Determine which pipeline(s) run on PRs for this repository. Common patterns:
- Repository name matches pipeline slug
- Monorepo may have pipeline named after the main repo
**Step 2: Find builds for the current branch**
Use MCP tools to list recent builds:
```javascript
mcp__MCPProxy__call_tool('buildkite:list_builds', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
branch: '<branch-name>',
detail_level: 'summary',
});
```
Or use bktide:
```bash
npx bktide builds --format json <org>/<pipeline>
```
**Step 3: Progressive disclosure of status**
Follow this pattern when examining builds:
1. **Overall state** - Is it `passed`, `failed`, `running`, `blocked`, or `canceled`?
2. **Job summary** - How many jobs passed/failed/broken?
3. **Annotations** (if present) - Check for test failures, warnings, or errors
4. **Failed job details** - Get logs for actually failed jobs (not just "broken")
### 4. Post-Push Monitoring Workflow
After pushing code, follow this workflow to monitor the CI build:
**Step 1: Find builds for the pushed commit**
Use the find-commit-builds script:
```bash
~/.claude/skills/buildkite-status/scripts/find-commit-builds.js <org> <commit-sha>
```
Or manually search using MCP tools with commit filter.
**Step 2: Monitor the build**
**Option A (Preferred): Use MCP wait_for_build tool**
```javascript
mcp__MCPProxy__call_tool('buildkite:wait_for_build', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
timeout: 1800,
poll_interval: 30,
});
```
This will:
- Poll every 30 seconds (configurable with `poll_interval`)
- Report status changes
- Complete when build reaches terminal state (passed/failed/canceled)
- Timeout after 30 minutes (configurable with `timeout`)
**Option B (Fallback): Use wait-for-build.js script**
If you prefer background execution:
```bash
~/.claude/skills/buildkite-status/scripts/wait-for-build.js <org> <pipeline> <build-number> --timeout 1800 --interval 30
```
**If the script fails** (e.g., bktide dependency error), use Option A - the MCP tool is more reliable.
**Step 3: Check on progress**
Periodically check the background job or wait for it to complete. When it finishes, check the exit code:
- 0 = passed
- 1 = failed
- 2 = canceled
- 3 = timeout
**Step 4: Investigate failures**
If the build failed, follow the "### 1. Investigating a Build from URL" workflow above.
### 5. Investigating Failures (Deprecated)
**Note**: This workflow is deprecated. Use "### 1. Investigating a Build from URL" and "### 2. Retrieving Job Logs" instead for a more complete investigation process.
When a build has failed, use this systematic approach:
**Step 1: Get build overview**
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
detail_level: 'detailed',
job_state: 'failed', // Only show failed jobs
});
```
This gives you:
- Overall build state
- Job summary (how many failed vs broken)
- List of failed jobs only
**Step 2: Check annotations**
Some projects put test failures in annotations:
```javascript
mcp__MCPProxy__call_tool('buildkite:list_annotations', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
});
```
Look for annotations with `style: "error"` or `style: "warning"`.
**Important**: Not all projects use annotations. See [references/annotation-patterns.md](references/annotation-patterns.md) for project-specific patterns.
**Step 3: Examine failed jobs**
For each failed job (not "broken" - see state reference below):
1. Get the job details from the build data
2. Check the job's log output
3. Look for stack traces, error messages, or test failures
**Step 4: Understand "broken" vs "failed"**
**Critical**: A job showing as "broken" is often NOT a failure. It typically means:
- The job was skipped because an earlier job failed
- The job's dependencies weren't met
- Conditional pipeline logic determined the job wasn't needed
See [references/buildkite-states.md](references/buildkite-states.md) for complete state explanations.
**Example**: In large monorepos, many jobs show "broken" because they were skipped due to file changes not affecting them. This is normal and expected.
### 6. Checking Blocked Builds
When a build is in `blocked` state, it's waiting for manual approval:
**Step 1: Identify the block step**
Get the build with `detail_level: "detailed"` and look for jobs with `state: "blocked"`.
**Step 2: Review what's being blocked**
Block steps typically have a `label` describing what approval is needed (e.g., "Deploy to Production").
**Step 3: Unblock if appropriate**
Use the MCP tool to unblock:
```javascript
mcp__MCPProxy__call_tool('buildkite:unblock_job', {
org_slug: '<org>',
pipeline_slug: '<pipeline>',
build_number: '<build-number>',
job_id: '<job-id>',
fields: {}, // Optional form fields if the block step has inputs
});
```
## Understanding Buildkite States
Buildkite has several states that can be confusing. Here's a quick reference:
### Build States
- `passed` - All jobs completed successfully ✅
- `failed` - One or more jobs failed ❌
- `running` - Build is currently executing 🔄
- `blocked` - Waiting for manual approval 🚫
- `canceled` - Build was canceled ⛔
### Job States
- `passed` - Job succeeded ✅
- `failed` - Job failed with non-zero exit ❌
- `broken` - **MISLEADING**: Usually means skipped due to pipeline logic, NOT a failure ⚠️
- `soft_failed` - Failed but marked as non-blocking 〰️
- `skipped` - Job was skipped ⏭️
**For complete state reference and project-specific patterns**, read [references/buildkite-states.md](references/buildkite-states.md).
## Progressive Disclosure Pattern
Always follow this pattern when checking build status:
1. **Start broad**: Overall build state (passed/failed/running)
2. **Check summary**: Job counts (how many passed/failed/broken)
3. **Check annotations**: If present, they often contain key information
4. **Drill into failures**: Only examine failed jobs (not broken)
5. **Read logs**: Get actual error messages and stack traces
Don't immediately jump to logs - the build state and annotations often tell you what you need to know.
## Project-Specific Patterns
### Large Projects / Monorepos
- **Use annotations heavily**: Test failures are usually summarized in annotations
- **Many "broken" jobs**: Normal due to conditional execution
- **Complex job graphs**: Jobs have dependencies and conditional logic
- **Check annotations first**: They save time vs reading all logs
### Small Projects
- **No annotations**: All information is in job logs
- **Simpler job structure**: Fewer dependencies and conditions
- **"Broken" is unusual**: May indicate an actual problem
- **Read logs directly**: No annotations to summarize failures
## Anti-Patterns: What NOT to Do
### ❌ Falling Back to GitHub Tools
**Don't**: Use `gh pr view`, `gh pr checks`, or GitHub API to check Buildkite status
**Why**: GitHub shows Buildkite check summary only. You lose:
- Real-time build logs and output
- Annotations with test failure details
- Job-level breakdown and states
- Ability to distinguish "broken" (skipped) from "failed"
- Direct build monitoring and waiting
- Proper state information
**Reality**: Always use Buildkite tools. GitHub summarizes; Buildkite is the source of truth.
### ❌ Abandoning Skill on Tool Failure
**Don't**: "The script failed, so I'll use GitHub tools instead"
**Why**: The skill documents MULTIPLE tool tiers:
- MCP tools (primary, always available)
- bktide CLI (secondary, convenience)
- Scripts (tertiary, helpers)
**Reality**: One tool failing doesn't invalidate the skill. Follow the fallback hierarchy - move to MCP tools, don't abandon Buildkite entirely.
### ❌ Emergency Override Rationalization
**Don't**: "This is urgent, I don't have time to follow the skill"
**Why**: Skills exist ESPECIALLY for high-pressure situations. Disciplined workflows prevent mistakes when you're rushed. Making wrong tool choices under pressure wastes MORE time debugging.
**Reality**: Following the skill is FASTER than recovering from wrong decisions. Taking 2 minutes to use the right tool saves 20 minutes of confusion.
### ❌ "I Already Know X" Rationalization
**Don't**: "I already know `gh pr view` works, why learn Buildkite tools?"
**Why**: Familiarity ≠ effectiveness. You'll spend more time working around GitHub's limitations than learning the proper tools.
**Reality**: Invest 2 minutes learning Buildkite MCP tools once. Save hours across all future builds.
## Red Flags - STOP
If you catch yourself thinking ANY of these thoughts, you're about to violate this skill:
- "The script failed, so the skill doesn't apply"
- "This is an emergency, no time for the skill"
- "I already know gh pr view works"
- "GitHub tools show the same information"
- "I'll just check GitHub quickly"
- "One tool failed, so I'll use what I know"
- "The skill is for normal situations, not emergencies"
- "I don't have time to learn new tools right now"
**These are rationalizations. Stop. Follow the tool hierarchy. Use Buildkite MCP tools.**
## Common Mistakes to Avoid
1. **Treating "broken" as "failed"**: Broken usually means skipped, not failed
2. **Ignoring annotations**: They often contain the most actionable information
3. **Not filtering by state**: Use `job_state: "failed"` to focus on actual failures
4. **Missing blocked builds**: A blocked build won't progress without manual intervention
5. **Polling in foreground**: Use MCP `wait_for_build` tool or background scripts
## Tips for Efficient Status Checking
1. **Use detail levels**: Start with `detail_level: "summary"` to reduce data
2. **Filter by job state**: Request only failed jobs when investigating
3. **Background monitoring**: Run wait-for-build.js in background after pushing
4. **Check annotations first**: For projects that use them, they're faster than logs
5. **Trust the scripts**: The bundled scripts handle polling, timeouts, and edge cases
## Resources
### References
- **[buildkite-states.md](references/buildkite-states.md)** - Complete guide to Buildkite states, including the misleading "broken" state and project-specific patterns
- **[annotation-patterns.md](references/annotation-patterns.md)** - How different projects use annotations and when to check them
- **[tool-capabilities.md](references/tool-capabilities.md)** - Comprehensive capability matrix for MCP tools, bktide, and scripts
- **[url-parsing.md](references/url-parsing.md)** - Understanding Buildkite URLs, step IDs vs job UUIDs
- **[troubleshooting.md](references/troubleshooting.md)** - Common errors, solutions, and decision tree for when stuck
### Scripts
- **[wait-for-build.js](scripts/wait-for-build.js)** - Background monitoring with timeout and polling
- **[find-commit-builds.js](scripts/find-commit-builds.js)** - Find builds for a specific commit
- **[get-build-logs.js](scripts/get-build-logs.js)** - Helper for log retrieval with UUID resolution (placeholder)
- **[parse-buildkite-url.js](scripts/parse-buildkite-url.js)** - Extract components from Buildkite URLs
Run scripts with `--help` for usage information.

View File

@@ -0,0 +1,196 @@
# Buildkite Annotation Patterns
Annotations are build-level messages that appear at the top of a build page. They can contain success messages, warnings, errors, or informational content. **Not all projects use annotations consistently.**
## What Are Annotations?
Annotations are created by build steps using the `buildkite-agent annotate` command. They appear prominently at the top of the build page and can be styled with different colors/icons.
### Annotation Styles
- **`success`** - Green, checkmark icon, positive message
- **`info`** - Blue, info icon, informational message (most common)
- **`warning`** - Yellow, warning icon, something to be aware of
- **`error`** - Red, error icon, indicates problems
## Project-Specific Patterns
### Projects That Use Annotations Heavily (e.g., zen-payroll)
These projects surface important information in annotations:
1. **Test Failures**: RSpec, Jest, or other test failures may be summarized in annotations
- Failed test count
- Links to failed test files
- Stack traces or error messages
2. **Coverage Reports**: Code coverage changes or drops below thresholds
3. **Linting Errors**: Rubocop, ESLint violations grouped by severity
4. **Build Resources**: Links to documentation, help channels, or common issues
5. **Security Scans**: Dependency vulnerabilities, security warnings
6. **Performance Issues**: Slow tests, memory issues, or other performance concerns
**When checking status**: Always look at annotations first for these projects. They often contain the most actionable information.
### Projects Without Annotations (e.g., gusto-karafka)
Smaller or simpler projects may not use annotations at all. For these projects:
- **All failure information is in job logs**: Must read individual job output
- **No centralized summary**: Need to check each failed job separately
- **Simpler debugging path**: Less information to parse, but more manual work
## Accessing Annotations
### Via MCP Tools
```javascript
// List all annotations for a build
mcp__MCPProxy__call_tool('buildkite:list_annotations', {
org_slug: 'gusto',
pipeline_slug: 'zenpayroll',
build_number: '1359675',
});
```
Annotation response includes:
- `context`: Unique identifier for the annotation
- `style`: success/info/warning/error
- `body_html`: HTML content of the annotation
- `created_at`: Timestamp
### Via bktide
```bash
npx bktide annotations gusto/zenpayroll#1359675
```
## Interpreting Annotations
### 1. Start with Error-Styled Annotations
Check for `style: "error"` first - these indicate critical problems:
- Test suite failures
- Build failures
- Security issues
### 2. Check Warning Annotations
`style: "warning"` may indicate:
- Degraded performance
- Coverage drops
- Flaky tests
- Deprecated dependencies
### 3. Info Annotations for Context
`style: "info"` often contains:
- Build metadata
- Links to resources
- Change summaries
- Help information
### 4. Success Annotations
`style: "success"` indicates:
- All tests passed
- Coverage improved
- Performance metrics good
## Common Annotation Patterns
### Test Failure Annotations
Typically include:
```
❌ 15 tests failed
spec/models/user_spec.rb
- validates email format
- validates password strength
spec/controllers/api_controller_spec.rb
- returns 401 when unauthorized
```
**Action**: Read the listed test failures, then examine the job logs for full details.
### Build Resource Annotations
```
Having problems with your build?
- Check build documentation: [link]
- Ask in #build-stability Slack channel
```
**Action**: These are informational - reference them if you're stuck debugging.
### Coverage Annotations
```
⚠️ Code coverage decreased by 2.5%
Current: 85.3% | Previous: 87.8%
```
**Action**: May or may not be actionable depending on project policy.
## When Annotations Are Missing
If a build has no annotations:
1. **Don't assume success**: Check the overall build state
2. **Look at job logs**: All failure information will be in individual jobs
3. **Check job states**: Failed jobs will have `state: "failed"`
4. **Read failed job logs**: Use MCP tools or bktide to get logs
## Inconsistencies Across Projects
Be aware that annotation usage varies wildly:
- **Some projects**: Every failure is annotated
- **Some projects**: Only critical failures annotated
- **Some projects**: No annotations at all
- **Some projects**: Annotations are informational only, not diagnostic
**Never rely solely on annotations.** Always check:
1. Overall build state
2. Job states
3. Annotations (if present)
4. Job logs for failed jobs
## Example Workflows
### Checking a Failed Build With Annotations
1. Get build status → state is `failed`
2. List annotations → find error-styled annotation with test failures
3. Note which tests failed from annotation
4. Get detailed logs for failed job
5. Read stack traces and error messages
### Checking a Failed Build Without Annotations
1. Get build status → state is `failed`
2. Check job summary → identify which jobs failed
3. Get detailed information for each failed job
4. Read logs for each failed job
5. Identify root cause from logs
### Checking a Passing Build
1. Get build status → state is `passed`
2. Optionally check annotations for warnings or info
3. Note any "broken" jobs (may be expected)
4. No need to read logs unless investigating performance

View File

@@ -0,0 +1,107 @@
# Buildkite Build and Job States
Understanding Buildkite states is critical for correctly interpreting build status. Some states are misleading or require additional context.
## Build States
### Terminal States
- **`passed`** - All jobs completed successfully
- **`failed`** - One or more jobs failed
- **`canceled`** - Build was manually canceled
- **`skipped`** - Build was skipped (e.g., due to branch filters)
- **`blocked`** - Build is waiting for manual approval via block step
### Active States
- **`running`** - Build is currently executing
- **`scheduled`** - Build is queued and waiting to start
- **`creating`** - Build is being created
## Job States
### Terminal States
- **`passed`** - Job completed successfully
- **`failed`** - Job failed with non-zero exit code
- **`canceled`** - Job was canceled
- **`skipped`** - Job was skipped
- **`timed_out`** - Job exceeded time limit
### Special States (Often Misleading)
- **`broken`** - This is the most misleading state. It can mean:
- Job was skipped because an earlier job in the pipeline failed
- Job was skipped due to dependency conditions not being met
- Job was skipped due to conditional logic in the pipeline config
- **NOT necessarily a failure of this specific job**
Example: In the zen-payroll pipeline, many jobs show as "broken" but are actually skipped because their dependencies indicated they weren't needed (e.g., no relevant file changes).
- **`soft_failed`** - Job failed but was marked as "soft fail" (doesn't block pipeline)
- Shows as failed but doesn't cause overall build failure
- Often used for optional checks or flaky tests
### Active States
- **`waiting`** - Job is waiting for dependencies
- **`waiting_failed`** - Job was waiting but its dependency failed
- **`assigned`** - Job has been assigned to an agent
- **`accepted`** - Agent has accepted the job
- **`running`** - Job is currently executing
- **`blocked`** - Job is a block step waiting for manual unblock
## Interpreting Build Status
### Progressive Disclosure Pattern
When checking build status, follow this pattern:
1. **Start with overall state**: `passed`, `failed`, `canceled`, `blocked`
2. **If failed, check job summary**: How many jobs failed vs broken vs passed?
3. **Examine failed jobs specifically**: Don't assume "broken" means the job itself failed
4. **Check annotations**: Some projects surface important failures in annotations
5. **Inspect logs**: For actual failures, read the job logs
### Common Pitfalls
1. **Treating "broken" as "failed"**: A "broken" job is often just skipped due to pipeline logic, not an actual failure.
2. **Ignoring soft fails**: Jobs marked as `soft_failed` may contain important information even though they don't block the build.
3. **Missing blocked builds**: A `blocked` build is waiting for approval and won't progress without manual intervention.
4. **Overlooking job dependencies**: Jobs may be skipped (`broken`) because their dependencies weren't met, which is expected behavior.
## Project-Specific Patterns
### zen-payroll Pipeline
- **Heavy use of conditional execution**: Many jobs are conditionally skipped based on file changes
- **"broken" is normal**: A build with many "broken" jobs may still be perfectly healthy
- **Check annotations**: Important test failures are often surfaced in build annotations
- **Multiple test suites**: Different test types (unit, integration, system) have different failure patterns
### Smaller Pipelines (e.g., gusto-karafka)
- **Fewer conditional jobs**: Most jobs are expected to run
- **"broken" usually indicates a problem**: Less conditional logic means broken jobs are more likely to be actual issues
- **Simpler job graphs**: Easier to trace why a job didn't run
- **May not use annotations**: Failures are usually just in job logs
## When to Investigate
Investigate a build when:
1. Overall build state is `failed`
2. Jobs show `failed` state (not just `broken`)
3. Build is `blocked` and you need to unblock it
4. Annotations contain error messages
5. Job logs show actual errors (red output, stack traces, test failures)
Don't automatically investigate when:
1. Build is `passed` (even if some jobs are `broken`)
2. Jobs are `soft_failed` unless specifically requested
3. Jobs are `broken` due to conditional execution (check pipeline config)

View File

@@ -0,0 +1,291 @@
# Tool Capabilities Reference
This document provides complete capability information for all Buildkite status checking tools.
## Overview
Three tool categories exist with different strengths and limitations:
1. **MCP Tools** - Direct Buildkite API access via Model Context Protocol
2. **bktide CLI** - Human-readable command-line tool (npm package)
3. **Bundled Scripts** - Helper wrappers in this skill's `scripts/` directory
## Capability Matrix
| Capability | MCP Tools | bktide | Scripts | Notes |
| --------------------- | ------------------------------- | ----------------------- | -------------------------- | -------------------------- |
| List organizations | ✅ `buildkite:list_orgs` | ❌ | ❌ | |
| List pipelines | ✅ `buildkite:list_pipelines` | ✅ `bktide pipelines` | ❌ | |
| List builds | ✅ `buildkite:list_builds` | ✅ `bktide builds` | ✅ `find-commit-builds.js` | Scripts are specialized |
| Get build details | ✅ `buildkite:get_build` | ✅ `bktide build` | ❌ | |
| Get annotations | ✅ `buildkite:list_annotations` | ✅ `bktide annotations` | ❌ | |
| **Retrieve job logs** | **`buildkite:get_logs`** | **❌ NO** | **`get-build-logs.js`** | **bktide cannot get logs** |
| Get log metadata | ✅ `buildkite:get_logs_info` | ❌ | ❌ | |
| List artifacts | ✅ `buildkite:list_artifacts` | ❌ | ❌ | |
| Wait for build | ✅ `buildkite:wait_for_build` | ❌ | ✅ `wait-for-build.js` | MCP preferred |
| Unblock jobs | ✅ `buildkite:unblock_job` | ❌ | ❌ | |
| Real-time updates | ✅ | ❌ | ✅ | Via polling |
| Human-readable output | ❌ (JSON) | ✅ | Varies | |
| Works offline | ❌ | ❌ | ❌ | All need network |
| Requires auth | ✅ (MCP config) | ✅ (BK_TOKEN) | ✅ (uses bktide) | |
## Detailed Tool Information
### MCP Tools (Primary)
**Access Method:** `mcp__MCPProxy__call_tool("buildkite:<tool>", {...})`
**Authentication:** Configured in MCP server settings (typically uses `BUILDKITE_API_TOKEN`)
**Pros:**
- Complete API coverage
- Always available (no external dependencies)
- Real-time data
- Structured JSON responses
**Cons:**
- Verbose JSON output
- Requires parsing for human reading
**Key Tools:**
#### `buildkite:get_build`
Get detailed build information including job states, timing, and metadata.
Parameters:
- `org_slug` (required): Organization slug
- `pipeline_slug` (required): Pipeline slug
- `build_number` (required): Build number
- `detail_level` (optional): "summary" | "detailed" | "complete"
- `job_state` (optional): Filter jobs by state ("failed", "passed", etc.)
Returns: Build object with jobs array, state, timing, author, etc.
#### `buildkite:get_logs`
**THE CRITICAL TOOL** - Retrieve actual log output from a job.
Parameters:
- `org_slug` (required): Organization slug
- `pipeline_slug` (required): Pipeline slug
- `build_number` (required): Build number
- `job_id` (required): Job UUID (NOT step ID from URL)
Returns: Log text content
**Common Issues:**
- "job not found" → Using step ID instead of job UUID
- Empty response → Job hasn't started or finished yet
#### `buildkite:wait_for_build`
Poll build until completion.
Parameters:
- `org_slug` (required): Organization slug
- `pipeline_slug` (required): Pipeline slug
- `build_number` (required): Build number
- `timeout` (optional): Seconds until timeout (default: 1800)
- `poll_interval` (optional): Seconds between checks (default: 30)
Returns: Final build state when complete or timeout
### bktide CLI (Secondary)
**Access Method:** `npx bktide <command>`
**Authentication:** `BK_TOKEN` environment variable or `~/.bktide/config`
**Pros:**
- Human-readable colored output
- Intuitive command structure
- Good for interactive terminal work
**Cons:**
- External npm dependency
- **CANNOT retrieve job logs** (most critical limitation)
- Limited compared to full API
- Requires npx/node installed
**Key Commands:**
```bash
npx bktide pipelines <org> # List pipelines
npx bktide builds <org>/<pipeline> # List recent builds
npx bktide build <org>/<pipeline>/<number> # Build details
npx bktide build <org>/<pipeline>/<number> --jobs # Show job summary
npx bktide build <org>/<pipeline>/<number> --failed # Show failed jobs only
npx bktide annotations <org>/<pipeline>/<number> # Show annotations
```
**Critical**: bktide has NO command for retrieving logs. The `build` command shows job states and names, but NOT log content.
### Bundled Scripts (Tertiary)
**Access Method:** `~/.claude/skills/buildkite-status/scripts/<script>.js`
**Authentication:** Use bktide internally (requires `BK_TOKEN`)
**Pros:**
- Purpose-built for specific workflows
- Handle common use cases automatically
- Provide structured output
**Cons:**
- Depend on bktide (external dependency)
- Limited to specific use cases
- May have version compatibility issues
**Available Scripts:**
#### `find-commit-builds.js`
Find builds matching a specific commit SHA.
Usage:
```bash
~/.claude/skills/buildkite-status/scripts/find-commit-builds.js <org> <commit-sha>
```
Returns: JSON array of matching builds
#### `wait-for-build.js`
Monitor build until completion (background-friendly).
Usage:
```bash
~/.claude/skills/buildkite-status/scripts/wait-for-build.js <org> <pipeline> <build> [options]
```
Options:
- `--timeout <seconds>`: Max wait time (default: 1800)
- `--interval <seconds>`: Poll interval (default: 30)
Exit codes:
- 0: Build passed
- 1: Build failed
- 2: Build canceled
- 3: Timeout
#### `get-build-logs.js` (NEW - to be implemented)
Retrieve logs for a failed job with automatic UUID resolution.
Usage:
```bash
~/.claude/skills/buildkite-status/scripts/get-build-logs.js <org> <pipeline> <build> <job-label-or-uuid>
```
Features:
- Accepts job label or UUID
- Automatically resolves label → UUID
- Handles step ID confusion
- Formats output for readability
## Decision Matrix: Which Tool to Use
### Use MCP Tools When:
- Getting build details
- **Retrieving job logs** (ONLY option with bktide)
- Waiting for builds (preferred over script)
- Unblocking jobs
- Automating workflows
- Need structured data
### Use bktide When:
- Interactive terminal work
- Want human-readable summary
- Listing pipelines/builds
- Getting quick status overview
- **NOT when you need logs** (it can't do this)
### Use Scripts When:
- Need specialized workflow (find commits)
- Want background monitoring
- MCP tools fail (fallback)
- Automating repetitive tasks
## Common Mistakes
### ❌ Trying to get logs with bktide
**Don't**: `npx bktide build <org>/<pipeline>/<number> --logs`
**Why**: This flag doesn't exist. bktide cannot retrieve logs.
**Do**: Use `buildkite:get_logs` MCP tool
### ❌ Using step ID for log retrieval
**Don't**: Extract `sid=019a5f...` from URL and use directly
**Why**: Step IDs ≠ Job UUIDs. MCP tools need job UUIDs.
**Do**: Call `buildkite:get_build` to get job details, extract `uuid` field
### ❌ Abandoning MCP tools when script fails
**Don't**: "Script failed, I'll use GitHub instead"
**Why**: Scripts depend on bktide. MCP tools are independent.
**Do**: Use MCP tools directly when scripts fail
## Troubleshooting
### Issue: "job not found" when calling get_logs
**Diagnosis**: Using step ID instead of job UUID
**Solution**:
1. Call `buildkite:get_build` with `detail_level: "detailed"`
2. Find job by `label` field
3. Extract `uuid` field
4. Use that UUID in `get_logs` call
### Issue: bktide command not found
**Diagnosis**: npm/npx not installed or not in PATH
**Solution**:
1. Use MCP tools instead (preferred)
2. Or install: `npm install -g @anthropic/bktide`
### Issue: Empty logs returned
**Diagnosis**: Job hasn't completed or logs not available yet
**Solution**:
1. Check job `state` - should be terminal (passed/failed/canceled)
2. Wait for job to finish
3. Check job `started_at` and `finished_at` timestamps
## See Also
- [SKILL.md](../SKILL.md) - Main skill documentation
- [troubleshooting.md](troubleshooting.md) - Common errors and solutions
- [url-parsing.md](url-parsing.md) - Understanding Buildkite URLs

View File

@@ -0,0 +1,365 @@
# Buildkite Status Troubleshooting
Common errors when working with Buildkite and how to resolve them.
## MCP Tool Errors
### Error: "job not found"
**When**: Calling `buildkite:get_logs`
**Cause**: Using step ID from URL instead of job UUID from API
**Solution**:
1. Call `buildkite:get_build` with `detail_level: "detailed"`
2. Find job by `label` field
3. Extract `uuid` field (NOT the `id` field)
4. Use that UUID in `get_logs`
**Example**:
```javascript
// ❌ Wrong - using step ID from URL
mcp__MCPProxy__call_tool('buildkite:get_logs', {
job_id: '019a5f23-8109-4656-a033-bd62a82ca239', // This is a step ID
});
// ✅ Correct - get job UUID from API first
const build = await mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '29627',
detail_level: 'detailed',
});
const job = build.jobs.find(
(j) => j.label === 'ste rspec' && j.state === 'failed'
);
await mcp__MCPProxy__call_tool('buildkite:get_logs', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '29627',
job_id: job.uuid, // This is the correct job UUID
});
```
**See Also**: [url-parsing.md](url-parsing.md) for step ID vs job UUID explanation
---
### Error: "build not found" or "pipeline not found"
**When**: Calling any MCP tool
**Cause**: Incorrect org slug or pipeline slug format
**Common Mistakes**:
- Using repository name instead of pipeline slug
- Including org name in pipeline slug
- Using display name instead of URL slug
**Solution**:
Extract slugs from URL correctly:
```
https://buildkite.com/gusto/payroll-building-blocks/builds/123
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
org pipeline slug
```
**Slug Format Rules**:
- All lowercase
- Hyphens instead of underscores
- No spaces
- No special characters
**Example**:
```javascript
// ❌ Wrong
{ org_slug: "Gusto", pipeline_slug: "Payroll Building Blocks" }
// ✅ Correct
{ org_slug: "gusto", pipeline_slug: "payroll-building-blocks" }
```
---
### Error: Empty logs returned
**When**: Calling `buildkite:get_logs`
**Causes**:
1. Job hasn't started yet
2. Job is still running
3. Job failed before producing output
4. Logs not available yet (eventual consistency)
**Diagnosis**:
Check job state first:
```javascript
const build = await mcp__MCPProxy__call_tool('buildkite:get_build', {
detail_level: 'detailed',
});
const job = build.jobs.find((j) => j.uuid === jobUuid);
console.log(job.state); // Should be terminal: passed/failed/canceled
console.log(job.started_at); // Should not be null
console.log(job.finished_at); // Should not be null for terminal state
```
**Solution**:
- If state is `waiting` or `running`: Wait for job to complete
- If state is terminal but logs empty: Wait a few seconds for eventual consistency
- If still empty: Job may have failed immediately (check exit_status)
---
### Error: "Unauthorized" or "Forbidden"
**When**: Any MCP tool call
**Cause**: Authentication or permission issue
**Diagnosis Steps**:
1. Check MCP server configuration:
```bash
# MCP server should have BUILDKITE_API_TOKEN configured
```
2. Verify token has correct scope:
- `read_builds` - Required for reading build info
- `read_build_logs` - Required for log retrieval
- `read_pipelines` - Required for pipeline listing
3. Check organization access:
- Token must have access to the specific organization
- Some orgs require SSO
**Solution**:
- Verify BUILDKITE_API_TOKEN in MCP config
- Generate new token at https://buildkite.com/user/api-access-tokens
- Ensure token has required scopes
- Report to human partner if still failing (may need org admin help)
---
## bktide CLI Errors
### Error: "bktide: command not found"
**Cause**: bktide not installed or not in PATH
**Solution**:
Use MCP tools instead (preferred):
```javascript
// Instead of: npx bktide build gusto/payroll-building-blocks/123
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '123',
});
```
Or install bktide:
```bash
npm install -g @anthropic/bktide
```
---
### Error: "Cannot read logs with bktide"
**Cause**: bktide does not have log retrieval capability
**Solution**:
Use MCP tools for logs:
```javascript
mcp__MCPProxy__call_tool('buildkite:get_logs', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
build_number: '123',
job_id: '<job-uuid>',
});
```
**See Also**: [tool-capabilities.md](tool-capabilities.md) for complete capability matrix
---
## Script Errors
### Error: Script fails with "bktide error"
**Cause**: Scripts depend on bktide internally
**Solution**:
1. Use equivalent MCP tool instead (preferred)
2. Or ensure bktide is installed and configured
3. Or check `BK_TOKEN` environment variable is set
**Example**:
```bash
# Script failing
~/.claude/skills/buildkite-status/scripts/wait-for-build.js gusto payroll-building-blocks 123
# Use MCP tool instead
mcp__MCPProxy__call_tool("buildkite:wait_for_build", {
org_slug: "gusto",
pipeline_slug: "payroll-building-blocks",
build_number: "123",
timeout: 1800,
poll_interval: 30
})
```
---
## Build State Confusion
### Issue: Many jobs show "broken" but build looks healthy
**Cause**: "broken" doesn't mean failed - it usually means skipped
**Explanation**:
Buildkite uses "broken" state for:
- Jobs skipped because dependency failed
- Jobs skipped due to conditional logic
- Jobs skipped because file changes didn't affect them
**Solution**:
Filter for actual failures:
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
detail_level: 'detailed',
job_state: 'failed', // Only show actually failed jobs
});
```
**See Also**: [buildkite-states.md](buildkite-states.md) for complete state explanations
---
### Issue: Build shows "failed" but all jobs passed
**Cause**: A "soft_failed" job counts as passed in job list but failed for build state
**Solution**:
Check for soft failures:
```javascript
const build = await mcp__MCPProxy__call_tool('buildkite:get_build', {
detail_level: 'detailed',
});
const softFails = build.jobs.filter((j) => j.soft_failed === true);
console.log(softFails); // These caused build to fail but are marked non-blocking
```
---
## Common Workflow Issues
### Issue: Cannot find recent build for branch
**Cause**: Build may be filtered or pipeline has many builds
**Solution**:
Use branch filter and increase limit:
```javascript
mcp__MCPProxy__call_tool('buildkite:list_builds', {
org_slug: 'gusto',
pipeline_slug: 'payroll-building-blocks',
branch: 'my-feature-branch',
per_page: 20, // Default may be smaller
});
```
Or find by commit:
```bash
~/.claude/skills/buildkite-status/scripts/find-commit-builds.js gusto <commit-sha>
```
---
### Issue: Multiple jobs have same label, can't tell which failed
**Cause**: Parallelized jobs have same base label
**Solution**:
Jobs with same label are numbered:
- "rspec (1/10)"
- "rspec (2/10)"
Match on full label including partition:
```javascript
const failedJob = build.jobs.find(
(j) => j.label === 'rspec (2/10)' && j.state === 'failed'
);
```
Or find all failed jobs with that label:
```javascript
const failedRspecJobs = build.jobs.filter(
(j) => j.label.startsWith('rspec (') && j.state === 'failed'
);
```
---
## Decision Tree: What to Do When Stuck
```
Unable to investigate build failure?
├─ Can't get build details
│ ├─ Check URL format → [url-parsing.md]
│ ├─ Check org/pipeline slugs → lowercase, hyphenated
│ └─ Check auth → BUILDKITE_API_TOKEN configured
├─ Can't get job logs
│ ├─ Using bktide? → Use MCP tools instead [tool-capabilities.md]
│ ├─ Getting "job not found"? → Using step ID instead of job UUID [url-parsing.md]
│ ├─ Empty logs? → Check job state (started_at, finished_at)
│ └─ Still failing? → Report to human partner (may be auth/permission)
├─ Confused about job states
│ ├─ Many "broken" jobs? → Normal, means skipped [buildkite-states.md]
│ ├─ "soft_failed"? → Failed but non-blocking
│ └─ Can't find failed job? → Filter with job_state: "failed"
└─ Tool not working
├─ MCP tool error? → Check auth, verify slugs
├─ bktide error? → Use MCP tools instead
└─ Script error? → Use MCP tools directly
```
## See Also
- [SKILL.md](../SKILL.md) - Main skill documentation
- [tool-capabilities.md](tool-capabilities.md) - What each tool can do
- [url-parsing.md](url-parsing.md) - Understanding URLs and IDs
- [buildkite-states.md](buildkite-states.md) - Build and job states

View File

@@ -0,0 +1,272 @@
# Buildkite URL Parsing Reference
This document explains Buildkite URL formats and how to extract information from them.
## URL Formats
Buildkite uses several URL patterns for builds and jobs:
### Build URL (Most Common)
```
https://buildkite.com/{org}/{pipeline}/builds/{number}
```
Example:
```
https://buildkite.com/gusto/payroll-building-blocks/builds/29627
```
Extracting:
- `org`: "gusto"
- `pipeline`: "payroll-building-blocks"
- `number`: "29627"
### Step/Job URL (From Build Page)
```
https://buildkite.com/{org}/{pipeline}/builds/{number}/steps/{view}?sid={step-id}
```
Example:
```
https://buildkite.com/gusto/payroll-building-blocks/builds/29627/steps/canvas?sid=019a5f23-8109-4656-a033-bd62a82ca239
```
Extracting:
- `org`: "gusto"
- `pipeline`: "payroll-building-blocks"
- `number`: "29627"
- `view`: "canvas" (UI view type)
- `sid`: "019a5f23-8109-4656-a033-bd62a82ca239" (step ID)
**IMPORTANT**: The `sid` (step ID) is NOT the same as job UUID. See "Step IDs vs Job UUIDs" below.
### Job Detail URL
```
https://buildkite.com/{org}/{pipeline}/builds/{number}/jobs/{job-uuid}
```
Example:
```
https://buildkite.com/gusto/payroll-building-blocks/builds/29627/jobs/019a5f20-2d30-4c67-9edd-87fb92e1f487
```
Extracting:
- `org`: "gusto"
- `pipeline`: "payroll-building-blocks"
- `number`: "29627"
- `job-uuid`: "019a5f20-2d30-4c67-9edd-87fb92e1f487"
**NOTE**: This format contains the actual job UUID needed for log retrieval.
## Step IDs vs Job UUIDs
**Critical distinction**: Buildkite has two types of identifiers that are easily confused.
### Step IDs
- **Source**: Query parameter `sid` in step URLs
- **Format**: ULID format (e.g., `019a5f23-8109-4656-a033-bd62a82ca239`)
- **Purpose**: Frontend UI routing
- **Use**: Navigating to specific steps in web UI
- **API Usage**: ❌ NOT accepted by MCP tools
### Job UUIDs
- **Source**: `uuid` field in API responses
- **Format**: ULID format (e.g., `019a5f20-2d30-4c67-9edd-87fb92e1f487`)
- **Purpose**: Backend job identification
- **Use**: API calls to get logs, job details, etc.
- **API Usage**: ✅ Required by MCP `get_logs` tool
### Why the Confusion?
Both use ULID format (starts with `019a5f...`), but:
- Step IDs come from URLs → Web UI routing
- Job UUIDs come from API responses → Backend identification
**You cannot use a step ID for log retrieval.** Always get job UUID from `buildkite:get_build` API.
## Resolving Step ID to Job UUID
When given a step URL with `sid` parameter:
**Step 1: Extract build identifiers**
```javascript
// From: https://buildkite.com/gusto/payroll-building-blocks/builds/29627/steps/canvas?sid=019a5f23...
const org = 'gusto';
const pipeline = 'payroll-building-blocks';
const build = '29627';
// Ignore the sid parameter
```
**Step 2: Get job details from API**
```javascript
mcp__MCPProxy__call_tool('buildkite:get_build', {
org_slug: org,
pipeline_slug: pipeline,
build_number: build,
detail_level: 'detailed',
job_state: 'failed', // If investigating failures
});
```
**Step 3: Match job by properties**
The API response includes all jobs. Match by:
- `label` field (e.g., "ste rspec", "Rubocop")
- `state` field (e.g., "failed")
- `type` field (e.g., "script")
- `step_key` field if available
**Step 4: Extract job UUID**
```javascript
// From API response
const job = response.jobs.find(
(j) => j.label === 'ste rspec' && j.state === 'failed'
);
const jobUuid = job.uuid; // e.g., "019a5f20-2d30-4c67-9edd-87fb92e1f487"
```
**Step 5: Use job UUID for logs**
```javascript
mcp__MCPProxy__call_tool('buildkite:get_logs', {
org_slug: org,
pipeline_slug: pipeline,
build_number: build,
job_id: jobUuid, // NOT the step ID from URL
});
```
## Parsing Logic
### Simple Regex Approach
```javascript
function parseBuildkiteUrl(url) {
// Match build URL pattern
const buildMatch = url.match(
/buildkite\.com\/([^/]+)\/([^/]+)\/builds\/(\d+)/
);
if (!buildMatch) {
throw new Error('Invalid Buildkite URL');
}
return {
org: buildMatch[1],
pipeline: buildMatch[2],
buildNumber: buildMatch[3],
};
}
// Usage
const info = parseBuildkiteUrl(
'https://buildkite.com/gusto/payroll-building-blocks/builds/29627'
);
// => { org: "gusto", pipeline: "payroll-building-blocks", buildNumber: "29627" }
```
### Extracting Step ID (If Needed)
```javascript
function parseStepUrl(url) {
const base = parseBuildkiteUrl(url);
// Extract step ID from query parameter
const sidMatch = url.match(/[?&]sid=([^&]+)/);
return {
...base,
stepId: sidMatch ? sidMatch[1] : null,
};
}
// Usage
const info = parseStepUrl(
'https://buildkite.com/gusto/payroll-building-blocks/builds/29627/steps/canvas?sid=019a5f23...'
);
// => { org: "gusto", pipeline: "payroll-building-blocks", buildNumber: "29627", stepId: "019a5f23..." }
```
**Remember**: The `stepId` is useful for debugging but cannot be used for API calls. Always fetch job UUID from the API.
## Common URL Patterns in Practice
### Pattern 1: User Shares Failing Build URL
**URL**: `https://buildkite.com/org/pipeline/builds/123`
**Workflow**:
1. Extract org/pipeline/build
2. Call `get_build` with `detail_level: "summary"`
3. Check build state
4. If failed, call `get_build` with `detail_level: "detailed"` and `job_state: "failed"`
5. Get logs for each failed job
### Pattern 2: User Shares Step URL (Clicked on Specific Job)
**URL**: `https://buildkite.com/org/pipeline/builds/123/steps/canvas?sid=019a5f23...`
**Workflow**:
1. Extract org/pipeline/build (ignore `sid`)
2. Call `get_build` with `detail_level: "detailed"`
3. Find job matching user's intent (often the failed one)
4. Extract job UUID
5. Get logs for that job
The `sid` hints at which job the user was looking at, but you must resolve it via the API.
### Pattern 3: User Provides Job UUID Directly
**URL**: `https://buildkite.com/org/pipeline/builds/123/jobs/019a5f20-...`
**Workflow**:
1. Extract org/pipeline/build/job-uuid
2. Call `get_logs` directly with the job UUID
3. No resolution needed - this is the actual job UUID
This is the ideal format but least common in practice.
## Edge Cases
### Multiple Jobs with Same Label
Some pipelines parallelize jobs:
- "rspec (1/10)"
- "rspec (2/10)"
- "rspec (3/10)"
When resolving, match the full label string including the partition number.
### Dynamic Pipeline Steps
Some pipelines generate steps dynamically. The step structure may not be predictable from the URL alone. Always query the API to see actual job structure.
### Retried Jobs
When jobs are retried, multiple job UUIDs exist for the same step. The API returns the most recent attempt. Check `retries_count` and `retry_source` fields if investigating retry behavior.
## See Also
- [SKILL.md](../SKILL.md) - Main skill documentation
- [tool-capabilities.md](tool-capabilities.md) - Tool limitations and capabilities
- [troubleshooting.md](troubleshooting.md) - Common errors

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env node
/**
* Find Buildkite builds for a specific commit
*
* Searches across pipelines for builds matching a commit SHA.
* Useful for post-push workflows to find which builds are running.
*
* Usage:
* find-commit-builds.js <org> <commit-sha> [options]
*
* Options:
* --pipeline <slug> Limit search to specific pipeline
* --branch <name> Limit to specific branch
* --format <json|plain> Output format (default: plain)
*
* Exit codes:
* 0 - Builds found
* 1 - No builds found
* 2 - Error occurred
*/
const { execSync } = require('child_process');
// Parse command line arguments
const args = process.argv.slice(2);
if (args.length < 2 || args[0] === '--help' || args[0] === '-h') {
console.error('Usage: find-commit-builds.js <org> <commit-sha> [options]');
console.error('Options:');
console.error(' --pipeline <slug> Limit search to specific pipeline');
console.error(' --branch <name> Limit to specific branch');
console.error(' --format <json|plain> Output format (default: plain)');
process.exit(2);
}
const org = args[0];
const commit = args[1];
// Parse options
let pipelineSlug = null;
let branch = null;
let format = 'plain';
for (let i = 2; i < args.length; i++) {
if (args[i] === '--pipeline' && i + 1 < args.length) {
pipelineSlug = args[++i];
} else if (args[i] === '--branch' && i + 1 < args.length) {
branch = args[++i];
} else if (args[i] === '--format' && i + 1 < args.length) {
format = args[++i];
}
}
function getPipelines() {
try {
const cmd = `npx bktide pipelines --format json ${org}`;
const output = execSync(cmd, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return JSON.parse(output);
} catch (error) {
throw new Error(`Failed to get pipelines: ${error.message}`);
}
}
function getBuildsForPipeline(pipeline, commit, branch) {
try {
let cmd = `npx bktide builds --format json ${org}/${pipeline}`;
if (commit) {
// Note: bktide might not support commit filtering directly,
// so we'll fetch recent builds and filter
cmd += ` --state running --state scheduled`;
}
const output = execSync(cmd, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
const builds = JSON.parse(output);
// Filter by commit
return builds.filter((build) => {
const commitMatch = !commit || build.commit?.startsWith(commit);
const branchMatch = !branch || build.branch === branch;
return commitMatch && branchMatch;
});
} catch (error) {
// Pipeline might not exist or be accessible, return empty array
return [];
}
}
function main() {
try {
const allBuilds = [];
if (pipelineSlug) {
// Search single pipeline
const builds = getBuildsForPipeline(pipelineSlug, commit, branch);
allBuilds.push(...builds);
} else {
// Search all pipelines
const pipelines = getPipelines();
for (const pipeline of pipelines) {
const builds = getBuildsForPipeline(pipeline.slug, commit, branch);
if (builds.length > 0) {
allBuilds.push(...builds);
}
}
}
if (format === 'json') {
console.log(JSON.stringify(allBuilds, null, 2));
} else {
if (allBuilds.length === 0) {
console.log(`No builds found for commit ${commit}`);
process.exit(1);
}
console.log(`Found ${allBuilds.length} build(s) for commit ${commit}:\n`);
for (const build of allBuilds) {
console.log(
` ${build.pipeline.slug}#${build.number} - ${build.state}`
);
console.log(` Branch: ${build.branch}`);
console.log(` URL: ${build.web_url}`);
console.log();
}
}
process.exit(allBuilds.length > 0 ? 0 : 1);
} catch (error) {
console.error('Error:', error.message);
process.exit(2);
}
}
main();

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env node
/**
* Get build logs for a specific job
*
* Usage:
* get-build-logs.js <org> <pipeline> <build> <job-label-or-uuid>
*
* Examples:
* get-build-logs.js gusto payroll-building-blocks 29627 "ste rspec"
* get-build-logs.js gusto payroll-building-blocks 29627 019a5f20-2d30-4c67-9edd-87fb92e1f487
*
* Features:
* - Accepts job label or UUID
* - Automatically resolves label to UUID if needed
* - Handles step ID vs job UUID confusion
* - Outputs formatted logs
*/
import { execSync } from 'child_process';
function usage() {
console.error(
'Usage: get-build-logs.js <org> <pipeline> <build> <job-label-or-uuid>'
);
console.error('');
console.error('Examples:');
console.error(
' get-build-logs.js gusto payroll-building-blocks 29627 "ste rspec"'
);
console.error(
' get-build-logs.js gusto payroll-building-blocks 29627 019a5f20-2d30-4c67-9edd-87fb92e1f487'
);
process.exit(1);
}
function getBuildDetails(org, pipeline, build) {
try {
const output = execSync(
`npx bktide build ${org}/${pipeline}/${build} --format json`,
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }
);
return JSON.parse(output);
} catch (error) {
console.error(`Error getting build details: ${error.message}`);
process.exit(1);
}
}
function isUuid(str) {
// UUIDs are 36 characters with specific format
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
str
);
}
function resolveJobUuid(buildDetails, jobLabelOrUuid) {
// If it looks like a UUID, assume it's a job UUID
if (isUuid(jobLabelOrUuid)) {
return jobLabelOrUuid;
}
// Otherwise treat as label and search for matching job
// Note: bktide JSON format needs to be checked - this is a placeholder
console.error(`Note: Searching for job with label "${jobLabelOrUuid}"`);
console.error(
`Note: This script is a placeholder and needs MCP tool integration`
);
console.error(`Note: Use MCP buildkite:get_logs directly instead:`);
console.error(``);
console.error(`mcp__MCPProxy__call_tool("buildkite:get_logs", {`);
console.error(` org_slug: "${buildDetails.organization.slug}",`);
console.error(` pipeline_slug: "${buildDetails.pipeline.slug}",`);
console.error(` build_number: "${buildDetails.number}",`);
console.error(` job_id: "<job-uuid>"`);
console.error(`})`);
process.exit(1);
}
function main() {
const args = process.argv.slice(2);
if (args.length < 4 || args.includes('--help') || args.includes('-h')) {
usage();
}
const [org, pipeline, build, jobLabelOrUuid] = args;
console.error(`Fetching build details for ${org}/${pipeline}/${build}...`);
const buildDetails = getBuildDetails(org, pipeline, build);
console.error(`Resolving job identifier...`);
const jobUuid = resolveJobUuid(buildDetails, jobLabelOrUuid);
console.error(`\nNote: This script is a placeholder.`);
console.error(`For actual log retrieval, use MCP tools directly:`);
console.error(``);
console.error(`mcp__MCPProxy__call_tool("buildkite:get_logs", {`);
console.error(` org_slug: "${org}",`);
console.error(` pipeline_slug: "${pipeline}",`);
console.error(` build_number: "${build}",`);
console.error(` job_id: "${jobUuid}"`);
console.error(`})`);
}
main();

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env node
/**
* Parse Buildkite URL to extract components
*
* Usage:
* parse-buildkite-url.js <url>
*
* Examples:
* parse-buildkite-url.js "https://buildkite.com/gusto/payroll-building-blocks/builds/29627"
* parse-buildkite-url.js "https://buildkite.com/gusto/payroll-building-blocks/builds/29627/steps/canvas?sid=019a5f23..."
*
* Output:
* JSON object with: org, pipeline, buildNumber, stepId (if present)
*/
function usage() {
console.error('Usage: parse-buildkite-url.js <url>');
console.error('');
console.error('Examples:');
console.error(
' parse-buildkite-url.js "https://buildkite.com/gusto/payroll-building-blocks/builds/29627"'
);
console.error(
' parse-buildkite-url.js "https://buildkite.com/gusto/payroll-building-blocks/builds/29627/steps/canvas?sid=019a5f..."'
);
process.exit(1);
}
function parseBuildkiteUrl(url) {
// Match build URL pattern
const buildMatch = url.match(
/buildkite\.com\/([^/]+)\/([^/]+)\/builds\/(\d+)/
);
if (!buildMatch) {
throw new Error(
'Invalid Buildkite URL - expected format: https://buildkite.com/{org}/{pipeline}/builds/{number}'
);
}
const result = {
org: buildMatch[1],
pipeline: buildMatch[2],
buildNumber: buildMatch[3],
};
// Check for step ID query parameter
const sidMatch = url.match(/[?&]sid=([^&]+)/);
if (sidMatch) {
result.stepId = sidMatch[1];
result.note =
'stepId is for UI routing only - use API to get job UUID for log retrieval';
}
// Check for job UUID in path
const jobMatch = url.match(/\/jobs\/([0-9a-f-]+)/i);
if (jobMatch) {
result.jobUuid = jobMatch[1];
result.note = 'jobUuid can be used directly for log retrieval';
}
return result;
}
function main() {
const args = process.argv.slice(2);
if (args.length !== 1 || args.includes('--help') || args.includes('-h')) {
usage();
}
const url = args[0];
try {
const parsed = parseBuildkiteUrl(url);
console.log(JSON.stringify(parsed, null, 2));
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,155 @@
#!/usr/bin/env node
/**
* Background build monitor for Buildkite
*
* Polls a build until it reaches a terminal state (passed, failed, canceled)
* with configurable timeout and polling interval.
*
* Usage:
* wait-for-build.js <org> <pipeline> <build-number> [options]
*
* Options:
* --timeout <seconds> Maximum time to wait (default: 1800 = 30 minutes)
* --interval <seconds> Polling interval (default: 30)
* --quiet Suppress progress updates
*
* Exit codes:
* 0 - Build passed
* 1 - Build failed
* 2 - Build canceled
* 3 - Timeout reached
* 4 - Error occurred
*/
const { execSync } = require('child_process');
// Parse command line arguments
const args = process.argv.slice(2);
if (args.length < 3 || args[0] === '--help' || args[0] === '-h') {
console.error(
'Usage: wait-for-build.js <org> <pipeline> <build-number> [options]'
);
console.error('Options:');
console.error(
' --timeout <seconds> Maximum time to wait (default: 1800)'
);
console.error(' --interval <seconds> Polling interval (default: 30)');
console.error(' --quiet Suppress progress updates');
process.exit(4);
}
const org = args[0];
const pipeline = args[1];
const buildNumber = args[2];
// Parse options
let timeout = 1800; // 30 minutes default
let interval = 30; // 30 seconds default
let quiet = false;
for (let i = 3; i < args.length; i++) {
if (args[i] === '--timeout' && i + 1 < args.length) {
timeout = parseInt(args[++i], 10);
} else if (args[i] === '--interval' && i + 1 < args.length) {
interval = parseInt(args[++i], 10);
} else if (args[i] === '--quiet') {
quiet = true;
}
}
const startTime = Date.now();
const timeoutMs = timeout * 1000;
function log(message) {
if (!quiet) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
function getBuildStatus() {
try {
const cmd = `npx bktide build --format json gusto/${pipeline}#${buildNumber}`;
const output = execSync(cmd, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return JSON.parse(output);
} catch (error) {
if (error.stdout) {
try {
return JSON.parse(error.stdout);
} catch {}
}
throw new Error(`Failed to get build status: ${error.message}`);
}
}
function isTerminalState(state) {
return ['passed', 'failed', 'canceled', 'blocked', 'skipped'].includes(state);
}
function getExitCode(state) {
switch (state) {
case 'passed':
return 0;
case 'failed':
return 1;
case 'canceled':
return 2;
default:
return 4;
}
}
log(`Monitoring build: ${org}/${pipeline}#${buildNumber}`);
log(`Timeout: ${timeout}s, Polling interval: ${interval}s`);
async function main() {
while (true) {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
// Check timeout
if (elapsed >= timeout) {
log(`Timeout reached after ${elapsed}s`);
process.exit(3);
}
try {
const build = getBuildStatus();
const state = build.state;
log(`Build state: ${state} (elapsed: ${elapsed}s)`);
if (isTerminalState(state)) {
log(`Build finished with state: ${state}`);
log(`Build URL: ${build.web_url}`);
// Show job summary if available
if (build.job_summary) {
const summary = build.job_summary;
log(`Jobs: ${summary.total} total`);
if (summary.by_state) {
const states = Object.entries(summary.by_state)
.map(([s, count]) => `${count} ${s}`)
.join(', ');
log(` ${states}`);
}
}
process.exit(getExitCode(state));
}
// Wait before next poll
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
} catch (error) {
log(`Error checking build status: ${error.message}`);
log('Retrying...');
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
}
}
}
main().catch((error) => {
console.error('Fatal error:', error.message);
process.exit(4);
});