Configure agent pr attestation
Skill harness/harness-skills/skills/configure-agent-pr-attestation
A collection of structured AI agent skills that enable Claude Code, Cursor, GitHub Copilot, and other AI coding assistants to create, operate, debug, and govern Harness CI/CD workflows through natural language.
npx -y skills add harness/harness-skills --skill configure-agent-pr-attestationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.0 KB, as published. Nobody here has run it
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_filterandurl_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
# 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:
{
"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.