CI/CD Integration

Agent Quick Context

  • Set PM_AUTHOR=ci-bot so CI-created items are clearly attributed
  • Set PM_PATH to isolate the CI tracker from the developer tracker
  • Use --json for machine-readable output; pipe to jq to extract IDs
  • Use --quiet to suppress progress spinners and banners in logs
  • Use --create-mode progressive to create items non-interactively
  • Disable telemetry in CI: set telemetry.enabled: false in settings or PM_TELEMETRY=0 env var
  • Item IDs are printed on the first line of pm create --json output 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 โ€” success
  • 1 โ€” 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
  }
}

CI/CD Integration local
Report an issue