CI/CD Integration
Agent Quick Context
- Set
PM_AUTHOR=ci-botso CI-created items are clearly attributed - Set
PM_PATHto isolate the CI tracker from the developer tracker - Use
--jsonfor machine-readable output; pipe tojqto extract IDs - Use
--quietto suppress progress spinners and banners in logs - Use
--create-mode progressiveto create items non-interactively - Disable telemetry in CI: set
telemetry.enabled: falsein settings orPM_TELEMETRY=0env var - Item IDs are printed on the first line of
pm create --jsonoutput as.id
Overview
Running pm-cli in CI lets you:
- Track automated work items โ create Tasks or Issues from failing tests, deployment steps, or security scans so the results are visible in the project tracker alongside manually created items.
- Link test results to items โ close or update items with evidence from CI runs, giving reviewers a clear audit trail.
- Record deployment evidence โ attach build numbers, commit SHAs, and environment details to Milestone or Event items at deploy time.
- Gate on item state โ block a merge if a required item is not closed, or automatically close items when a PR is merged.
Environment Setup
Essential environment variables
| Variable | Purpose | Example |
|---|---|---|
PM_AUTHOR |
Author name written to every item created or updated in CI | ci-bot |
PM_PATH |
Override the tracker path | /workspace/.agents/ci-pm |
PM_TELEMETRY |
Set to 0 to disable telemetry in CI |
0 |
PM_QUIET |
Set to 1 as an alternative to passing --quiet |
1 |
Settings for CI
Add these to .agents/pm/settings.json (or to the CI-specific settings file
pointed to by PM_PATH):
{
"author": "ci-bot",
"telemetry": {
"enabled": false
},
"output": {
"color": false
}
}
Useful flags
| Flag | Effect |
|---|---|
--json |
Output JSON instead of human-readable text |
--quiet |
Suppress banners, spinners, and progress lines |
--create-mode progressive |
Create items without interactive prompts |
--message "text" |
Append an audit message to the history entry |
GitHub Actions Examples
Create or update an item on a pull request event
This workflow creates a Task when a PR is opened and updates it (with a link to the PR) on every subsequent push to the PR branch.
# .github/workflows/pm-track-pr.yml
name: Track PR in pm
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
track:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pm-cli
run: npm install -g @unbrained/pm-cli
- name: Initialise tracker (if not already present)
run: pm init --skip-if-exists
env:
PM_AUTHOR: ci-bot
- name: Create or update PR tracking item
id: pm_item
run: |
TITLE="PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}"
# Try to find an existing item for this PR
EXISTING=$(pm search --json --filter "tags:pr-${{ github.event.pull_request.number }}" \
| jq -r '.[0].id // empty')
if [ -n "$EXISTING" ]; then
pm update "$EXISTING" \
--message "Updated by CI run ${{ github.run_id }}" \
--quiet
echo "item_id=$EXISTING" >> "$GITHUB_OUTPUT"
else
ITEM_ID=$(pm create \
--type Task \
--title "$TITLE" \
--tag "pr-${{ github.event.pull_request.number }}" \
--tag "automated" \
--create-mode progressive \
--json --quiet \
| jq -r '.id')
echo "item_id=$ITEM_ID" >> "$GITHUB_OUTPUT"
fi
env:
PM_AUTHOR: ci-bot
PM_TELEMETRY: "0"
- name: Print item ID
run: echo "Tracking item ${{ steps.pm_item.outputs.item_id }}"
Run linked tests and record results
This workflow runs tests, then updates the pm item with pass/fail evidence.
# .github/workflows/pm-test-results.yml
name: Test and record results
on:
push:
branches: [main, "release/**"]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Install pm-cli
run: npm install -g @unbrained/pm-cli
- name: Run tests
id: tests
run: |
npm test --reporter=json > test-results.json 2>&1
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
continue-on-error: true
- name: Record test results in pm
run: |
PASSED=$(jq '.numPassedTests' test-results.json)
FAILED=$(jq '.numFailedTests' test-results.json)
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
if [ "${{ steps.tests.outputs.exit_code }}" = "0" ]; then
STATUS_MSG="Tests passed: ${PASSED} passed, ${FAILED} failed."
else
STATUS_MSG="Tests FAILED: ${PASSED} passed, ${FAILED} failed."
fi
# Update or create a milestone item for this commit's test run
pm create \
--type Event \
--title "Test run: ${{ github.sha }}" \
--message "${STATUS_MSG} See ${RUN_URL}" \
--tag "test-results" \
--tag "sha-${{ github.sha }}" \
--create-mode progressive \
--quiet
env:
PM_AUTHOR: ci-bot
PM_TELEMETRY: "0"
- name: Fail the job if tests failed
if: steps.tests.outputs.exit_code != '0'
run: exit 1
Close items automatically on merge
This workflow closes the Task that was tracking a PR when the PR is merged.
# .github/workflows/pm-close-on-merge.yml
name: Close pm item on PR merge
on:
pull_request:
types: [closed]
jobs:
close_item:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pm-cli
run: npm install -g @unbrained/pm-cli
- name: Close tracking item
run: |
ITEM_ID=$(pm search --json \
--filter "tags:pr-${{ github.event.pull_request.number }}" \
| jq -r '.[0].id // empty')
if [ -n "$ITEM_ID" ]; then
pm close "$ITEM_ID" \
"Merged as PR #${{ github.event.pull_request.number }} โ ${{ github.event.pull_request.merge_commit_sha }}" \
--quiet
echo "Closed $ITEM_ID"
else
echo "No tracking item found for PR #${{ github.event.pull_request.number }}"
fi
env:
PM_AUTHOR: ci-bot
PM_TELEMETRY: "0"
GitLab CI Example
# .gitlab-ci.yml (relevant stages)
variables:
PM_AUTHOR: ci-bot
PM_TELEMETRY: "0"
stages:
- test
- record
test:
stage: test
image: node:20
script:
- npm ci
- npm test -- --reporter=json > test-results.json
artifacts:
paths:
- test-results.json
when: always
record-results:
stage: record
image: node:20
needs: [test]
when: always
script:
- npm install -g @unbrained/pm-cli
- pm init --skip-if-exists
- |
PASSED=$(jq '.numPassedTests' test-results.json)
FAILED=$(jq '.numFailedTests' test-results.json)
pm create \
--type Event \
--title "Test run: ${CI_COMMIT_SHORT_SHA}" \
--message "${PASSED} passed, ${FAILED} failed. Pipeline ${CI_PIPELINE_URL}" \
--tag "test-results" \
--tag "sha-${CI_COMMIT_SHORT_SHA}" \
--create-mode progressive \
--quiet
close-on-merge:
stage: record
image: node:20
only:
- merge_requests
variables:
GIT_STRATEGY: fetch
script:
- npm install -g @unbrained/pm-cli
- |
ITEM_ID=$(pm search --json \
--filter "tags:mr-${CI_MERGE_REQUEST_IID}" \
| jq -r '.[0].id // empty')
if [ -n "$ITEM_ID" ]; then
pm close "$ITEM_ID" \
"Merged as MR !${CI_MERGE_REQUEST_IID} โ ${CI_COMMIT_SHA}" \
--quiet
fi
when: on_success
Reading Machine Output
Using --json with jq
Every pm-cli command that produces output accepts --json. The output is
always a JSON object or array printed to stdout.
# Create an item and capture its ID
ITEM_ID=$(pm create \
--type Task \
--title "Deploy to staging" \
--create-mode progressive \
--json --quiet \
| jq -r '.id')
echo "Created: $ITEM_ID" # e.g. "Created: pm-3042"
Parsing a list of items
# Get IDs of all open Tasks tagged "deployment"
pm search --json --filter "type:Task status:open tags:deployment" \
| jq -r '.[].id'
Checking item status in a script
STATUS=$(pm show pm-3042 --json | jq -r '.status')
if [ "$STATUS" = "closed" ]; then
echo "Item is closed โ proceeding"
else
echo "Item is not closed (status: $STATUS) โ blocking deploy"
exit 1
fi
Exit codes
pm-cli exits with:
0โ success1โ command error (item not found, validation failure, etc.)2โ usage error (unknown flag, missing argument)
Scripts should check the exit code, not parse stderr.
Non-Interactive Patterns
By default, pm create prompts for missing required fields. In CI, suppress
all prompts with --create-mode progressive:
pm create \
--type Task \
--title "Automated security scan" \
--create-mode progressive \
--quiet
progressive mode fills in defaults for any fields not provided on the
command line and never blocks waiting for stdin.
Audit trails with --message
Pass --message to record the reason for a change in the item's history.
This is especially useful in CI so that the history log shows which pipeline
run made each change:
pm update pm-3042 \
--status in_progress \
--message "Started by pipeline #${CI_PIPELINE_ID:-$GITHUB_RUN_ID}" \
--quiet
Telemetry in CI
pm-cli collects anonymous usage telemetry by default. Disable it in CI to avoid noise in telemetry dashboards and to prevent network calls from slowing down pipelines.
Via environment variable (preferred for CI)
export PM_TELEMETRY=0
Or inline:
PM_TELEMETRY=0 pm create --type Task --title "..." --create-mode progressive --quiet
Via settings file
In .agents/pm/settings.json (or the CI-specific settings pointed to by
PM_PATH):
{
"telemetry": {
"enabled": false
}
}
Via a CI-only settings file
If you want to keep telemetry enabled for developers but not CI, use a separate settings file:
# In CI, point to a CI-specific tracker with telemetry off
export PM_PATH=/workspace/.agents/ci-pm
pm init --skip-if-exists
.agents/ci-pm/settings.json:
{
"author": "ci-bot",
"telemetry": {
"enabled": false
},
"output": {
"color": false
}
}