← Back to Cookbook

Track your own DORA + Code metrics with GitHub Actions → Recorder

A walk-through for pushing Deploy / LeadTime / Lines metrics from your own GitHub repo into the Recorder using a personal PAT. Get a feel for the Recorder API end-to-end. NASEBANAL runs the same shape internally across all 10 repos via a dedicated System User.

What this recipe records

Push DORA and code metrics from your own GitHub repo into the Recorder via GitHub Actions and a personal PAT. Also a good way to get a feel for the Recorder API end-to-end.

  • <MyApp>/DORA/Deploy — daily deploy count (daily cron)
  • <MyApp>/DORA/LeadTime — PR open → merge duration (per merge)
  • <MyApp>/Code/Lines — total LOC at HEAD (per merge)
  • <MyApp>/Code/PushDelta — net lines added/removed per merge

Pick any prefix for <MyApp> (e.g. MyTeam/..., Frontend/...). Each becomes its own tag in the Recorder, so the prefix is your namespace if you later want to compare multiple apps or teams side by side.

Prerequisites

  • A Recorder account
  • A GitHub repo where you can drop in workflow files

Setup

1. Mint a Recorder PAT

Go to Settings → API Tokens and create a PAT. The token string is only shown once in the issuance dialog — copy it immediately.

2. Register it as a GitHub Secret

In your repo's Settings → Secrets and variables → Actions, add a secret named RECORDER_PAT and paste the token from step 1.

# Or via gh CLI
gh secret set RECORDER_PAT --repo your-org/your-repo

3. Drop in the workflows

.github/workflows/post-on-deploy.yml (runs on each merge):

name: Post DORA metrics on deploy
 
on:
  push:
    branches: [main]
 
permissions:
  contents: read
  pull-requests: read
 
jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
 
      - name: Compute and post LeadTime + Lines
        env:
          RECORDER_PAT: ${{ secrets.RECORDER_PAT }}
          APP_PREFIX: MyApp  # pick your own prefix
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # PR open → merge duration (minutes)
          PR_NUM=$(gh pr list --state merged --base main --limit 1 --json number --jq '.[0].number')
          if [ -n "$PR_NUM" ]; then
            OPENED=$(gh pr view "$PR_NUM" --json createdAt --jq '.createdAt')
            MERGED=$(gh pr view "$PR_NUM" --json mergedAt --jq '.mergedAt')
            LEAD_MIN=$(( ($(date -d "$MERGED" +%s) - $(date -d "$OPENED" +%s)) / 60 ))
            curl -sS -X POST https://api.recorder.nasebanal.com/api/v1/records \
              -H "Authorization: Bearer $RECORDER_PAT" \
              -H "Content-Type: application/json" \
              -d "{\"name\":\"$APP_PREFIX/DORA/LeadTime\",\"unit\":\"min\",\"data_type\":\"integer\",\"chart_type\":\"line\",\"value\":$LEAD_MIN}"
          fi
 
          # Total LOC at HEAD
          LINES=$(git ls-files | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')
          curl -sS -X POST https://api.recorder.nasebanal.com/api/v1/records \
            -H "Authorization: Bearer $RECORDER_PAT" \
            -H "Content-Type: application/json" \
            -d "{\"name\":\"$APP_PREFIX/Code/Lines\",\"unit\":\"lines\",\"data_type\":\"integer\",\"chart_type\":\"line\",\"value\":$LINES}"

.github/workflows/post-daily.yml (one daily cron):

name: Post deploy count daily
 
on:
  schedule:
    - cron: '5 0 * * *'  # 00:05 UTC every day
  workflow_dispatch:
 
jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - name: Count yesterday's deploys
        env:
          RECORDER_PAT: ${{ secrets.RECORDER_PAT }}
          APP_PREFIX: MyApp
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SINCE=$(date -u -d 'yesterday 00:00' +%Y-%m-%dT%H:%M:%SZ)
          UNTIL=$(date -u -d 'today 00:00' +%Y-%m-%dT%H:%M:%SZ)
          COUNT=$(gh run list --workflow deploy.yml --created "$SINCE..$UNTIL" --json status --jq 'map(select(.status=="completed")) | length')
          curl -sS -X POST https://api.recorder.nasebanal.com/api/v1/records \
            -H "Authorization: Bearer $RECORDER_PAT" \
            -H "Content-Type: application/json" \
            -d "{\"name\":\"$APP_PREFIX/DORA/Deploy\",\"unit\":\"count\",\"data_type\":\"integer\",\"chart_type\":\"bar\",\"value\":$COUNT}"

4. Smoke test

Before relying on a merge, POST a single record by hand to confirm the token is wired up.

export RECORDER_PAT="the token you copied in step 1"
 
curl -sS -X POST https://api.recorder.nasebanal.com/api/v1/records \
  -H "Authorization: Bearer $RECORDER_PAT" \
  -H "Content-Type: application/json" \
  -d '{"name":"MyApp/DORA/Deploy","unit":"count","data_type":"integer","chart_type":"bar","value":1,"comment":"smoke test"}'

MyApp/DORA/Deploy should now appear in your Recorder tag list. Merge a PR and post-on-deploy.yml will start filling in the real metrics.

Sample output

After a week or two of data, the Recorder lets you line your metrics up as a time series. Below is NASEBANAL's own nb-recorder repo, so the tag prefix is System/... — yours would line up under your own <MyApp>/... prefix.

Lines and BranchDensity time-series chart in the Recorder

Left axis is Code/Lines (blue), right axis is Code/BranchDensity (yellow). LOC trending up while branch density edges down reads as "the codebase is growing, but the average density of if branches is thinning" — i.e. structural refactoring is keeping pace with growth.

⚠️ Secret handling

  • The PAT string is shown only once in the issuance dialog and cannot be re-displayed
  • Use the PAT only via GitHub Secrets — don't write it to a local file
  • If you suspect a leak, revoke the PAT in Recorder Settings → API Tokens, mint a new one, and overwrite the GitHub Secret