GitHub Actions

Hooks cover the machines you control. A CI check covers everyone: every pull request is searched against the decision record, and matching decisions are posted on the PR.

1. Create a read-only grant

Follow the one-time token setup on the Hooks page with two changes: name the client precedent-ci and keep the scope to decisions:read. CI never needs to draft. The grant acts as the person who approved it and is listed under Settings, Agents, where it can be revoked.

2. Store the secrets

  1. PRECEDENT_URL: your deployment, for example https://app.example.com
  2. PRECEDENT_CLIENT_ID: from the registration step
  3. PRECEDENT_REFRESH_TOKEN: from the token exchange
  4. PRECEDENT_ROTATE_PAT: a fine-grained GitHub token with Secrets write access on this repository, used to store the rotated refresh token after each run
The rotation secret is not optional. Refresh tokens rotate on every use and reusing an old one revokes the grant, so a workflow that cannot write the new token back will work exactly once. The concurrency group below stops two PRs racing for the same token.

3. Add the workflow

Save as .github/workflows/precedent.yml:

name: precedent
on: pull_request
concurrency: precedent-check   # rotation-safe: one run at a time
permissions:
  pull-requests: write
  contents: read

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Search the decision record with this diff
        env:
          BASE: ${{ secrets.PRECEDENT_URL }}
          CLIENT_ID: ${{ secrets.PRECEDENT_CLIENT_ID }}
          REFRESH: ${{ secrets.PRECEDENT_REFRESH_TOKEN }}
          GH_TOKEN: ${{ github.token }}
          ROTATE_TOKEN: ${{ secrets.PRECEDENT_ROTATE_PAT }}
        run: |
          SUMMARY=$(git log --format=%s origin/${{ github.base_ref }}..HEAD | head -5
                    git diff --stat origin/${{ github.base_ref }}...HEAD | tail -15)

          FRESH=$(curl -sf "$BASE/api/oauth/token" -d grant_type=refresh_token \
            -d refresh_token="$REFRESH" -d client_id="$CLIENT_ID")

          # Rotation: store the new refresh token before doing anything else.
          echo "$FRESH" | jq -r .refresh_token \
            | GH_TOKEN="$ROTATE_TOKEN" gh secret set PRECEDENT_REFRESH_TOKEN --repo "${{ github.repository }}"

          ACCESS=$(echo "$FRESH" | jq -r .access_token)
          BODY=$(jq -n --arg q "$SUMMARY" \
            '{jsonrpc:"2.0", id:1, method:"tools/call",
              params:{name:"search_decisions", arguments:{query:$q, limit:5}}}')
          HITS=$(curl -sf "$BASE/mcp" \
            -H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" \
            -H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: 2025-06-18" \
            -d "$BODY" \
            | jq -r '.result.structuredContent.decisions[]?
                     | select(.status == "decided")
                     | "- [\(.ref) \(.title)](\(.url))"')

          if [ -n "$HITS" ]; then
            gh pr comment ${{ github.event.number }} --body "$(printf \
              'This change touches decided topics. Cite them in the PR, or open a supersession:\n\n%s' "$HITS")"
          fi

Comment or fail?

The workflow above comments and lets the merge proceed, which is the right starting point: hybrid search surfaces decisions that are related, and a human or the reviewing agent judges whether the change actually conflicts. Once the comments have earned trust, make it blocking by replacing the comment step with exit 1 and marking the job required in branch protection. Teams that want the judgement automated too can have their review agent read the comment; its verdict cites the decision either way.