| 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.
command -v harness-scs
command -v jq
command -v git
command -v cosign
If harness-scs is missing, tell the user:
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":
- Ask for the exact command they run.
- Ask for an example PR URL their tool prints.
- 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:
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:
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:
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):
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.
#!/bin/bash
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:
{
"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.