---
name: configure-agent-pr-attestation
description: >-
  Use when asked to set up PR attestation, install harness-scs attestation hook,
  configure agent attestor, troubleshoot why agent attestation isn't uploading,
  or reconfigure an existing attestation hook (different SCM, new cosign key, updated Harness identity).
  Trigger phrases: set up PR attestation, install harness-scs attestation hook, configure agent attestor,
  why isn't my agent attestation uploading, set up agent attestation, configure PR signing,
  install attestation hook, configure claude attestation.
metadata:
  author: Harness
  version: 1.0.0
  mcp-server: harness-mcp-v2
license: Apache-2.0
compatibility: Requires harness-scs CLI binary and cosign for signing. Currently supports Claude Code as the agent runtime.
---

# Configure Agent PR Attestation

Sets up a `PostToolUse` hook that signs and uploads an in-toto attestation
to the Harness Evidence Vault every time an AI coding agent creates a PR/MR.

## Instructions

Follow these steps in exact order. Do NOT skip steps or reorder them.

### Step 1: Check Prerequisites

Run these checks and report what's missing. Do not proceed until all are installed.

```bash
command -v harness-scs
command -v jq
command -v git
command -v cosign
```

If `harness-scs` is missing, tell the user:

```bash
task compile-harness-scs && sudo mv harness-scs /usr/local/bin/
```

### Step 2: Ask for SCM

Ask the user which tool they use to create PRs/MRs. Use `AskUserQuestion`.

Options:

| Choice | command_filter | url_pattern |
| --- | --- | --- |
| Harness CLI (codepulse pr create) | `codepulse pr create` | `https://[^/]*\.harness\.io/.*/pulls/[0-9]+(/.*)?` |
| GitHub CLI (gh pr create) | `gh pr create` | `https://github\.com/[^ ]+/pull/[0-9]+` |
| GitLab CLI (glab mr create) | `glab mr create` | `https://[^ ]+/-/merge_requests/[0-9]+` |
| Bitbucket CLI (bb pr create) | `bb pr create` | `https://[^ ]+/pull-requests/[0-9]+` |
| Azure DevOps CLI (az repos pr create) | `az repos pr create` | `https://[^ ]+/_git/[^ ]+/pullrequest/[0-9]+` |
| Gitea / Forgejo (tea pr create) | `tea pr create` | `https://[^ ]+/pulls/[0-9]+` |

If user picks GitLab, Bitbucket, Gitea, or Custom — also ask if self-hosted.
If self-hosted, ask for hostname and replace the generic `[^ ]+` host in the
url_pattern with their specific escaped hostname.

If user picks "Other / custom":
1. Ask for the exact command they run.
2. Ask for an example PR URL their tool prints.
3. Derive `command_filter` and `url_pattern`, show to user, confirm.

### Step 3: Set up Harness Identity (auth.json)

Check if `~/.harness/auth.json` already exists:

```bash
test -r ~/.harness/auth.json && jq -e '.token and .account_id' ~/.harness/auth.json >/dev/null 2>&1
```

**If auth.json exists and is valid:** Tell the user "Found existing ~/.harness/auth.json" and move to Step 4.

**If auth.json does NOT exist:** Ask the user for these values:

- API key (PAT, format: `pat.<account>.<random>.<random>`)
- API URL (default: `https://app.harness.io`)
- Account ID (can be auto-derived from PAT if empty)
- Org ID
- Project ID

Then create `~/.harness/auth.json`:

```bash
mkdir -p ~/.harness
cat > ~/.harness/auth.json << 'EOF'
{
  "token": "<API key>",
  "base_url": "<API URL>",
  "account_id": "<Account ID>",
  "org_id": "<Org ID>",
  "project_id": "<Project ID>"
}
EOF
chmod 600 ~/.harness/auth.json
```

### Step 4: Set up Cosign Key and scs.env

Ask the user via `AskUserQuestion`:

```
Cosign signing key:
  - I already have one         → ask for absolute path
  - Generate a new one for me  → generate in ~/.harness/
```

**If generating:**

```bash
mkdir -p ~/.harness
cd ~/.harness && COSIGN_PASSWORD="" cosign generate-key-pair
chmod 600 ~/.harness/cosign.key
```

Set `key_path="$HOME/.harness/cosign.key"`.

**If existing key:** Ask for the absolute path. Validate it's readable.
Warn if password-protected — they'll need to set `COSIGN_PASSWORD` for unattended runs.

Then write `~/.harness/scs.env` (do NOT ask where to put it — always this path):

```bash
cat > ~/.harness/scs.env << 'EOF'
export HARNESS_SIGNER_KEY_PATH="<key_path>"
export COSIGN_PASSWORD=""
EOF
```

`scs.env` contains ONLY cosign signer variables. Never put Harness identity here.

### Step 5: Generate the Hook Script

Write to `<project>/.claude/hooks/agent-pr-attestation.sh`, substituting:

- `{{COMMAND_FILTER}}` ← from Step 2
- `{{URL_REGEX}}` ← from Step 2
- `{{KEY_PATH}}` ← from Step 4

Then `chmod +x` the script.

```bash
#!/bin/bash
# Generated by configure-agent-pr-attestation skill.
# Captures a signed in-toto attestation when an AI agent creates a PR.
#
# To reconfigure: re-run /configure-agent-pr-attestation in your agent.

set -uo pipefail

[ -f ~/.harness/scs.env ] && source ~/.harness/scs.env

COMMAND_FILTER='{{COMMAND_FILTER}}'
URL_REGEX='{{URL_REGEX}}'

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if ! echo "$COMMAND" | grep -qE "$COMMAND_FILTER"; then
  exit 0
fi

TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path')
TOOL_STDOUT=$(echo "$INPUT" | jq -r '.tool_response.stdout // empty')
TOOL_STDERR=$(echo "$INPUT" | jq -r '.tool_response.stderr // empty')
CWD=$(echo "$INPUT" | jq -r '.cwd')

PR_URL=$(printf '%s\n%s\n' "$TOOL_STDOUT" "$TOOL_STDERR" | grep -oE "$URL_REGEX" | head -1)
COMMIT_SHA=$(cd "$CWD" && git rev-parse HEAD 2>/dev/null)

if [ -z "$PR_URL" ] || [ -z "$COMMIT_SHA" ]; then
  exit 0
fi

PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
EXECUTION_ID="harness-pr-attestor-${PR_NUMBER:-$(date +%s)}"

PASSPHRASE_FILE=$(mktemp -t harness-scs-passphrase.XXXXXX)
trap 'rm -f "$PASSPHRASE_FILE"' EXIT
chmod 600 "$PASSPHRASE_FILE"
printf '%s' "${COSIGN_PASSWORD:-}" >"$PASSPHRASE_FILE"

harness-scs attestation run \
  --step "agent-pull-request" \
  --attestations claudecode \
  --attestor-claudecode-session-file "$TRANSCRIPT_PATH" \
  --attestor-claudecode-include-thinking \
  --attestor-claudecode-commit-sha "$COMMIT_SHA" \
  --attestor-claudecode-pr-url "$PR_URL" \
  --signer-file-key-path "${HARNESS_SIGNER_KEY_PATH:-{{KEY_PATH}}}" \
  --signer-file-key-passphrase-path "$PASSPHRASE_FILE" \
  --enable-attestation-upload \
  --execution-id "$EXECUTION_ID" \
  >/dev/null 2>&1

exit 0
```

### Step 6: Patch `.claude/settings.json`

Register the hook with Claude Code. Use project-scoped settings by default.

Read existing `.claude/settings.json` (create `{}` if absent), then merge in:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/agent-pr-attestation.sh"
          }
        ]
      }
    ]
  }
}
```

Make this idempotent — don't duplicate if already present.

After patching, confirm setup is complete and tell the user to verify by creating a test PR in a new session.

## Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Hook never fires | Matcher not registered, or `settings.json` malformed | `jq . .claude/settings.json` to validate; re-run skill |
| No attestation after PR created | `command_filter` doesn't match the command, OR `url_regex` doesn't match the PR URL | Add `echo "$COMMAND" > /tmp/debug-hook.txt` to inspect; run PR command manually and compare URL against regex |
| `harness-scs: command not found` | Binary not on PATH for non-interactive shells | Install to `/usr/local/bin/` or hardcode absolute path |
| `failed to load any signers` | Missing or unreadable cosign key | Check `$HARNESS_SIGNER_KEY_PATH` is set and file is readable |
| Upload fails with 401/403 | Stale or wrong API key | Check `~/.harness/auth.json` token and account_id |

## Reconfiguration

If the user re-runs the skill, detect existing files and offer:

- "An existing hook was found at `<path>`. Replace it?"
- "Existing cosign key at `<path>` — keep it or regenerate?"
- "Found `~/.harness/auth.json` — keep or update?"

Never silently overwrite.

## Limitations

Only cosign file keys are supported for signing. If the user asks about keyless signing (Fulcio), KMS signers, or non-Claude-Code agent runtimes, tell them these are not yet available.
