Skip to content

Instantly share code, notes, and snippets.

@ybenitezf
Last active April 19, 2026 19:53
Show Gist options
  • Select an option

  • Save ybenitezf/a9b23f97aef78c9f36234bd20023375f to your computer and use it in GitHub Desktop.

Select an option

Save ybenitezf/a9b23f97aef78c9f36234bd20023375f to your computer and use it in GitHub Desktop.
PI GitHub Actions Workflows - AI coding agent for Issues and PRs
name: PI Issue Agent
on:
issue_comment:
types: [created]
concurrency:
group: pi-issue-${{ github.event.issue.number }}
cancel-in-progress: true
permissions:
actions: read
issues: write
contents: write
pull-requests: write
jobs:
agent:
runs-on: ubuntu-latest
if: |
contains(github.server_url, 'github.com')
&& github.event.comment.user.type != 'Bot'
&& !contains(github.event.comment.body, '<!-- PI-BOT-COMMENT -->')
&& !github.event.issue.pull_request
steps:
- name: Check for @pi trigger
id: check-trigger
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Check if comment contains @pi with word boundaries (case-sensitive)
# Word boundary: start of string, whitespace, or punctuation before @pi
# and whitespace, punctuation, or end of string after @pi
if echo "$COMMENT_BODY" | grep -qP '(?<=^|[[:space:]])@pi(?=[[:space:]]|$|[[:punct:]])'; then
echo "triggered=true" >> $GITHUB_OUTPUT
else
echo "triggered=false" >> $GITHUB_OUTPUT
echo "Comment does not contain @pi trigger, skipping."
exit 0
fi
- name: Validate configuration
if: steps.check-trigger.outputs.triggered == 'true'
id: validate-config
env:
PI_SETTINGS: ${{ vars.PI_SETTINGS }}
PI_AUTH_JSON: ${{ secrets.PI_AUTH_JSON }}
run: |
MISSING=""
[ -z "$PI_SETTINGS" ] && MISSING="$MISSING PI_SETTINGS"
[ -z "$PI_AUTH_JSON" ] && MISSING="$MISSING PI_AUTH_JSON"
if [ -n "$MISSING" ]; then
echo "Required PI configuration not set:$MISSING"
echo "config_valid=false" >> $GITHUB_OUTPUT
else
echo "config_valid=true" >> $GITHUB_OUTPUT
fi
- name: Add eyes reaction
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_ID: ${{ github.event.comment.id }}
GITHUB_REPO: ${{ github.repository }}
run: |
gh api "repos/$GITHUB_REPO/issues/comments/$COMMENT_ID/reactions" \
-X POST \
-f content="eyes" || echo "Failed to add eyes reaction" >&2
- name: Setup Node.js
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
uses: actions/setup-node@v6
with:
node-version: '20.19.0'
- name: Install PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
run: npm install -g @mariozechner/pi-coding-agent
- name: Install OpenSpec CLI
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
run: npm install -g @fission-ai/openspec@latest
- name: Configure PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
env:
PI_SETTINGS: ${{ vars.PI_SETTINGS }}
PI_AUTH_JSON: ${{ secrets.PI_AUTH_JSON }}
run: |
mkdir -p ~/.pi/agent
mkdir -p ~/.pi/sessions
echo "$PI_AUTH_JSON" > ~/.pi/agent/auth.json
echo "$PI_SETTINGS" > ~/.pi/agent/settings.json
chmod 600 ~/.pi/agent/auth.json
- name: Find last PI-BOT-COMMENT
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: find-comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
GITHUB_REPO: ${{ github.repository }}
run: |
# Fetch all comments and find the last one with PI-BOT-COMMENT marker
COMMENTS=$(gh api "repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments" --paginate -q '.[] | select(.body | contains("<!-- PI-BOT-COMMENT -->")) | {id: .id, body: .body, created_at: .created_at}' | jq -s 'sort_by(.created_at) | last')
if [ -n "$COMMENTS" ] && [ "$COMMENTS" != "null" ]; then
echo "found=true" >> $GITHUB_OUTPUT
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$COMMENTS" | jq -r '.body' >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "found=false" >> $GITHUB_OUTPUT
fi
- name: Extract SESSION marker
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.find-comment.outputs.found == 'true'
id: extract-session
env:
COMMENT_BODY: ${{ steps.find-comment.outputs.body }}
run: |
# Extract artifact name and run ID from SESSION marker
SESSION_MARKER=$(echo "$COMMENT_BODY" | grep -oP '(?<=<!-- SESSION: ).*?(?= -->)' || true)
if [ -n "$SESSION_MARKER" ]; then
ARTIFACT_NAME=$(echo "$SESSION_MARKER" | grep -oP '(?<=artifact=)[^,]+' || true)
RUN_ID=$(echo "$SESSION_MARKER" | grep -oP '(?<=run=)[^,]+' || true)
if [ -n "$ARTIFACT_NAME" ] && [ -n "$RUN_ID" ]; then
echo "has_marker=true" >> $GITHUB_OUTPUT
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
else
echo "has_marker=false" >> $GITHUB_OUTPUT
fi
else
echo "has_marker=false" >> $GITHUB_OUTPUT
fi
- name: Download session artifact
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.extract-session.outputs.has_marker == 'true'
id: download-artifact
uses: actions/download-artifact@v4
with:
name: ${{ steps.extract-session.outputs.artifact_name }}
path: ~/.pi/sessions
run-id: ${{ steps.extract-session.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check session existence
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: session-check
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
SESSION_PATH="$HOME/.pi/sessions/issue-${ISSUE_NUMBER}.jsonl"
if [ -f "$SESSION_PATH" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
echo "path=$SESSION_PATH" >> $GITHUB_OUTPUT
- name: Checkout repository
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
uses: actions/checkout@v6
- name: Detect branch existence
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: branch-check
run: |
if git ls-remote --heads origin "${{ github.event.issue.number }}-pi-agent" | grep -q "${{ github.event.issue.number }}-pi-agent"; then
echo "branch_exists=true" >> $GITHUB_OUTPUT
else
echo "branch_exists=false" >> $GITHUB_OUTPUT
fi
- name: Checkout
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
run: |
if [[ "${{ steps.branch-check.outputs.branch_exists }}" == "true" ]]; then
git fetch origin "${{ github.event.issue.number }}-pi-agent"
git checkout "${{ github.event.issue.number }}-pi-agent"
else
git checkout ${{ github.event.repository.default_branch }}
git checkout -b "${{ github.event.issue.number }}-pi-agent"
fi
- name: Build prompt (first run)
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.session-check.outputs.exists == 'false'
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
COMMENT_BODY: ${{ github.event.comment.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
COMMENT_WITHOUT_COMMAND=$(echo "$COMMENT_BODY" | sed -E 's/(^|[[:space:]])@pi([[:space:]]|[[:punct:]]|$)/\1/; s/^[[:space:]]*//')
{
echo "You are running in a github action runner as an"
echo "async coding agent."
echo ""
echo "Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}"
echo ""
echo "Description:"
echo "${ISSUE_BODY}"
echo ""
echo "User request:"
echo "${COMMENT_WITHOUT_COMMAND}"
} > /tmp/prompt.txt
- name: Build prompt (subsequent run)
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.session-check.outputs.exists == 'true'
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
COMMENT_WITHOUT_COMMAND=$(echo "$COMMENT_BODY" | sed -E 's/(^|[[:space:]])@pi([[:space:]]|[[:punct:]]|$)/\1/; s/^[[:space:]]*//')
echo "$COMMENT_WITHOUT_COMMAND" > /tmp/prompt.txt
- name: Run PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: run-agent
env:
SESSION_PATH: ${{ steps.session-check.outputs.path }}
run: |
RESPONSE=$(cat /tmp/prompt.txt | pi -p --session "$SESSION_PATH" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=$RESPONSE" >> $GITHUB_OUTPUT
fi
echo "response<<EOF" >> $GITHUB_ENV
echo "$RESPONSE" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Configure git user
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
run: |
git config user.name "PI Agent"
git config user.email "pi-agent@example.com"
- name: Check for file changes
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: check-changes
run: |
if [[ -n "$(git status --porcelain)" ]]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Add rocket reaction
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_ID: ${{ github.event.comment.id }}
GITHUB_REPO: ${{ github.repository }}
run: |
gh api "repos/$GITHUB_REPO/issues/comments/$COMMENT_ID/reactions" \
-X POST \
-f content="rocket" || echo "Failed to add rocket reaction" >&2
- name: Commit and push changes
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
id: commit-push
run: |
git add -A
# Let the PI agent handle the commit
pi -p "$COMMIT_PROMPT" 2>&1
# Push the commit
git push --set-upstream origin "${{ github.event.issue.number }}-pi-agent" || exit 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_PROMPT: |
Review the staged changes and commit them with an appropriate conventional commit message.
Guidelines:
- Use conventional commit format: feat:, fix:, refactor:, docs:, test:, chore:, etc.
- Write a concise but descriptive message
- Focus on WHAT changed and WHY, not HOW
- Use git commit -m 'message' to commit the changes
- name: Define metadata prompt
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false'
id: define-metadata-prompt
run: |
ISSUE_NUM=${{ github.event.issue.number }}
cat << METADATA_EOF > /tmp/metadata_prompt.txt
Compare the current branch with main and generate PR metadata.
Output ONLY a JSON object with no other text, no markdown fences, no explanation.
The JSON must have this schema:
{
"title": "A concise conventional-commit style title for the PR",
"description": "A detailed description of the changes including a '## Changes' section and 'Related to #N' based on the issue number"
}
Correct output example:
{"title": "feat: add user authentication", "description": "## Changes\n\n- Added OAuth2 authentication flow\n- Implemented token refresh\n\nRelated to #42"}
Incorrect output example (DO NOT DO THIS):
```json
{"title": "feat: add auth", "description": "..."}
```
Instructions:
- Compare the current branch ($ISSUE_NUM-pi-agent) against main
- Examine the diff to understand what changed
- Generate a meaningful title that summarizes the actual changes
- Write a description that clearly explains what was done and why
- Include "Related to #$ISSUE_NUM" in the description
- Output ONLY valid JSON, nothing else
METADATA_EOF
echo "prompt_file=/tmp/metadata_prompt.txt" >> $GITHUB_OUTPUT
- name: Generate PR metadata
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false'
id: generate-metadata
run: |
METADATA_RESPONSE=$(pi -p "$(cat '${{ steps.define-metadata-prompt.outputs.prompt_file }}')" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=$METADATA_RESPONSE" >> $GITHUB_OUTPUT
echo "Failed to generate PR metadata" >&2
exit 1
fi
TITLE=$(echo "$METADATA_RESPONSE" | jq -r '.title')
DESCRIPTION=$(echo "$METADATA_RESPONSE" | jq -r '.description')
if [ -z "$TITLE" ] || [ "$TITLE" = "null" ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=Agent did not return valid JSON with title field" >> $GITHUB_OUTPUT
echo "Failed to parse PR title: agent did not return valid JSON" >&2
exit 1
fi
if [ -z "$DESCRIPTION" ] || [ "$DESCRIPTION" = "null" ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=Agent did not return valid JSON with description field" >> $GITHUB_OUTPUT
echo "Failed to parse PR description: agent did not return valid JSON" >&2
exit 1
fi
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "description<<EOF" >> $GITHUB_OUTPUT
echo "$DESCRIPTION" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create PR for new branch
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false' && steps.generate-metadata.outputs.error != 'true'
id: create-pr
run: |
# Create PR with generated metadata
PR_URL=$(gh pr create --title "$TITLE" --body "$DESCRIPTION")
echo "PR URL: $PR_URL"
# Extract PR number from URL (format: https://github.com/owner/repo/pull/123)
PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$')
echo "PR Number: $PR_NUMBER"
# Store as output for subsequent steps
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TITLE: ${{ steps.generate-metadata.outputs.title }}
DESCRIPTION: ${{ steps.generate-metadata.outputs.description }}
- name: Create PR for new branch (fallback)
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false' && steps.generate-metadata.outputs.error == 'true'
id: create-pr-fallback
run: |
# Fallback: create PR with default title and agent response as body
PR_URL=$(gh pr create --title "PI Agent: Changes for issue #${{ github.event.issue.number }}" --body "$RESPONSE")
echo "PR URL: $PR_URL"
PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$')
echo "PR Number: $PR_NUMBER"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Comment on issue with PR link
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false' && (steps.create-pr.outputs.pr_number != '' || steps.create-pr-fallback.outputs.pr_number != '')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
GITHUB_REPO: ${{ github.repository }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number || steps.create-pr-fallback.outputs.pr_number }}
run: |
COMMENT_BODY=$(printf '%s\n\n%s\n\n%s\n\n- PR #%s' \
"<!-- PI-BOT-COMMENT -->" \
"πŸ”€ **Pull Request Created**" \
"A pull request has been created to address this issue:" \
"$PR_NUMBER")
gh api "repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments" -f body="$COMMENT_BODY"
- name: Comment on PR with issue reference
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.branch-check.outputs.branch_exists == 'false' && (steps.create-pr.outputs.pr_number != '' || steps.create-pr-fallback.outputs.pr_number != '')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number || steps.create-pr-fallback.outputs.pr_number }}
GITHUB_REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
COMMENT_BODY=$(printf '%s\n\n%s\n\nThis pull request is related to issue #%s' \
"<!-- PI-BOT-COMMENT -->" \
"πŸ“‹ **Related Issue**" \
"$ISSUE_NUMBER")
gh api "repos/$GITHUB_REPO/issues/$PR_NUMBER/comments" -f body="$COMMENT_BODY"
- name: Post error comment
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.run-agent.outputs.error == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
GITHUB_REPO: ${{ github.repository }}
ERROR_MSG: ${{ steps.run-agent.outputs.error_message }}
ARTIFACT_NAME: pi-session-issue-${{ github.event.issue.number }}-${{ github.run_id }}
RUN_ID: ${{ github.run_id }}
run: |
ERROR_BODY="<!-- PI-BOT-COMMENT -->\n<!-- SESSION: artifact=${ARTIFACT_NAME},run=${RUN_ID} -->\n\n⚠️ **Error**\n\nAn error occurred:\n\`\`\`\n$ERROR_MSG\n\`\`\`"
gh api "repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments" -f body="$ERROR_BODY"
- name: Upload session artifact
if: (steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true') || cancelled()
id: upload-artifact
uses: actions/upload-artifact@v4
with:
name: pi-session-issue-${{ github.event.issue.number }}-${{ github.run_id }}
path: ~/.pi/sessions
retention-days: 7
if-no-files-found: ignore
- name: Post response comment
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.run-agent.outputs.error != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
GITHUB_REPO: ${{ github.repository }}
RESPONSE: ${{ env.response }}
ARTIFACT_NAME: pi-session-issue-${{ github.event.issue.number }}-${{ github.run_id }}
RUN_ID: ${{ github.run_id }}
run: |
{
echo "<!-- PI-BOT-COMMENT -->"
echo "<!-- SESSION: artifact=${ARTIFACT_NAME},run=${RUN_ID} -->"
echo ""
printf '%s' "$RESPONSE"
} > /tmp/response_body.txt
gh api "repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments" \
-f body="$(cat /tmp/response_body.txt)"
name: PI PR Agent
on:
pull_request_review_comment:
types: [created]
issue_comment:
types: [created]
concurrency:
group: pi-pr-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true
permissions:
actions: read
issues: write
contents: write
pull-requests: write
jobs:
agent:
runs-on: ubuntu-latest
if: |
contains(github.server_url, 'github.com')
&& github.event.comment.user.type != 'Bot'
&& !contains(github.event.comment.body, '<!-- PI-BOT-COMMENT -->')
&& (
(github.event_name == 'pull_request_review_comment' && github.event.pull_request != null)
|| (github.event_name == 'issue_comment' && github.event.issue.pull_request != null)
)
steps:
- name: Check for @pi trigger
id: check-trigger
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Check if comment contains @pi with word boundaries (case-sensitive)
# Word boundary: start of string, whitespace, or punctuation before @pi
# and whitespace, punctuation, or end of string after @pi
if echo "$COMMENT_BODY" | grep -qP '(?<=^|[[:space:]])@pi(?=[[:space:]]|$|[[:punct:]])'; then
echo "triggered=true" >> $GITHUB_OUTPUT
else
echo "triggered=false" >> $GITHUB_OUTPUT
echo "Comment does not contain @pi trigger, skipping."
exit 0
fi
- name: Validate configuration
if: steps.check-trigger.outputs.triggered == 'true'
id: validate-config
env:
PI_SETTINGS: ${{ vars.PI_SETTINGS }}
PI_AUTH_JSON: ${{ secrets.PI_AUTH_JSON }}
run: |
MISSING=""
[ -z "$PI_SETTINGS" ] && MISSING="$MISSING PI_SETTINGS"
[ -z "$PI_AUTH_JSON" ] && MISSING="$MISSING PI_AUTH_JSON"
if [ -n "$MISSING" ]; then
echo "Required PI configuration not set:$MISSING"
echo "config_valid=false" >> $GITHUB_OUTPUT
else
echo "config_valid=true" >> $GITHUB_OUTPUT
fi
- name: Add eyes reaction
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_ID: ${{ github.event.comment.id }}
GITHUB_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then
ENDPOINT="pulls/comments"
else
ENDPOINT="issues/comments"
fi
gh api "repos/$GITHUB_REPO/$ENDPOINT/$COMMENT_ID/reactions" \
-X POST \
-f content="eyes" || echo "Failed to add eyes reaction" >&2
- name: Normalize PR data
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: pr-data
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_STATE: ${{ github.event.pull_request.state }}
PR_MERGED: ${{ github.event.pull_request.merged }}
PR_BODY: ${{ github.event.pull_request.body }}
COMMENT_PATH: ${{ github.event.comment.path }}
COMMENT_LINE: ${{ github.event.comment.line }}
COMMENT_DIFF_HUNK: ${{ github.event.comment.diff_hunk }}
EVENT_NAME: ${{ github.event_name }}
REPO: ${{ github.repository }}
run: |
if [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then
# Direct access for review comments
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "head_ref=$PR_HEAD_REF" >> $GITHUB_OUTPUT
echo "title=$PR_TITLE" >> $GITHUB_OUTPUT
echo "state=$PR_STATE" >> $GITHUB_OUTPUT
echo "merged=$PR_MERGED" >> $GITHUB_OUTPUT
# Use jq for proper JSON escaping of PR body - use heredoc syntax for multi-line
echo "body<<PRBODYEOF" >> $GITHUB_OUTPUT
echo "$PR_BODY" | jq -Rs '.' >> $GITHUB_OUTPUT
echo "PRBODYEOF" >> $GITHUB_OUTPUT
# Build context JSON using jq for proper escaping
CONTEXT_JSON=$(jq -n \
--arg path "$COMMENT_PATH" \
--argjson line "${COMMENT_LINE:-null}" \
--arg diff_hunk "$COMMENT_DIFF_HUNK" \
'{path: $path, line: $line, diff_hunk: $diff_hunk}')
# Use heredoc syntax for multi-line context
echo "comment_context<<CTXT" >> $GITHUB_OUTPUT
echo "$CONTEXT_JSON" >> $GITHUB_OUTPUT
echo "CTXT" >> $GITHUB_OUTPUT
else
# API fetch for issue comments on PRs
PR_DATA=$(gh api "repos/$REPO/pulls/$PR_NUMBER")
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "head_ref=$(echo "$PR_DATA" | jq -r '.head.ref')" >> $GITHUB_OUTPUT
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> $GITHUB_OUTPUT
echo "state=$(echo "$PR_DATA" | jq -r '.state')" >> $GITHUB_OUTPUT
echo "merged=$(echo "$PR_DATA" | jq -r '.merged')" >> $GITHUB_OUTPUT
# Use jq for proper JSON escaping of PR body - use heredoc syntax for multi-line
PR_BODY_FETCHED=$(echo "$PR_DATA" | jq -r '.body')
echo "body<<PRBODYEOF" >> $GITHUB_OUTPUT
echo "$PR_BODY_FETCHED" | jq -Rs '.' >> $GITHUB_OUTPUT
echo "PRBODYEOF" >> $GITHUB_OUTPUT
# No review context for issue_comment events
echo 'comment_context={}' >> $GITHUB_OUTPUT
fi
- name: Validate PR state
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true'
id: validate-state
run: |
if [[ "${{ steps.pr-data.outputs.state }}" == "closed" ]] || [[ "${{ steps.pr-data.outputs.merged }}" == "true" ]]; then
echo "valid=false" >> $GITHUB_OUTPUT
echo "PR is closed or merged, skipping."
exit 0
else
echo "valid=true" >> $GITHUB_OUTPUT
fi
- name: Setup Node.js
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
uses: actions/setup-node@v6
with:
node-version: '20.19.0'
- name: Install PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
run: npm install -g @mariozechner/pi-coding-agent
- name: Install OpenSpec CLI
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
run: npm install -g @fission-ai/openspec@latest
- name: Configure PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
env:
PI_SETTINGS: ${{ vars.PI_SETTINGS }}
PI_AUTH_JSON: ${{ secrets.PI_AUTH_JSON }}
run: |
mkdir -p ~/.pi/agent
mkdir -p ~/.pi/sessions
echo "$PI_AUTH_JSON" > ~/.pi/agent/auth.json
echo "$PI_SETTINGS" > ~/.pi/agent/settings.json
chmod 600 ~/.pi/agent/auth.json
- name: Find last PI-BOT-COMMENT in PR
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
id: find-comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
run: |
# Fetch all issue comments on the PR and find the last one with PI-BOT-COMMENT marker
COMMENTS=$(gh api "repos/$GITHUB_REPO/issues/$PR_NUMBER/comments" --paginate -q '.[] | select(.body | contains("<!-- PI-BOT-COMMENT -->")) | {id: .id, body: .body, created_at: .created_at}' | jq -s 'sort_by(.created_at) | last')
if [ -n "$COMMENTS" ] && [ "$COMMENTS" != "null" ]; then
echo "found=true" >> $GITHUB_OUTPUT
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$COMMENTS" | jq -r '.body' >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "found=false" >> $GITHUB_OUTPUT
fi
- name: Extract SESSION marker
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.find-comment.outputs.found == 'true'
id: extract-session
env:
COMMENT_BODY: ${{ steps.find-comment.outputs.body }}
run: |
# Extract artifact name and run ID from SESSION marker
SESSION_MARKER=$(echo "$COMMENT_BODY" | grep -oP '(?<=<!-- SESSION: ).*?(?= -->)' || true)
if [ -n "$SESSION_MARKER" ]; then
ARTIFACT_NAME=$(echo "$SESSION_MARKER" | grep -oP '(?<=artifact=)[^,]+' || true)
RUN_ID=$(echo "$SESSION_MARKER" | grep -oP '(?<=run=)[^,]+' || true)
if [ -n "$ARTIFACT_NAME" ] && [ -n "$RUN_ID" ]; then
echo "has_marker=true" >> $GITHUB_OUTPUT
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
else
echo "has_marker=false" >> $GITHUB_OUTPUT
fi
else
echo "has_marker=false" >> $GITHUB_OUTPUT
fi
- name: Download session artifact
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.extract-session.outputs.has_marker == 'true'
id: download-artifact
uses: actions/download-artifact@v4
with:
name: ${{ steps.extract-session.outputs.artifact_name }}
path: ~/.pi/sessions
run-id: ${{ steps.extract-session.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check session existence
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
id: session-check
env:
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
run: |
SESSION_PATH="$HOME/.pi/sessions/pr-${PR_NUMBER}.jsonl"
if [ -f "$SESSION_PATH" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
echo "path=$SESSION_PATH" >> $GITHUB_OUTPUT
- name: Checkout PR branch
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
uses: actions/checkout@v6
with:
ref: ${{ steps.pr-data.outputs.head_ref }}
fetch-depth: 1
- name: Build prompt
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
env:
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_CONTEXT: ${{ steps.pr-data.outputs.comment_context }}
run: |
COMMENT_WITHOUT_COMMAND=$(echo "$COMMENT_BODY" | sed -E 's/(^|[[:space:]])@pi([[:space:]]|[[:punct:]]|$)/\1/; s/^[[:space:]]*//')
# Check if review comment context exists and is non-empty
if [ -n "$COMMENT_CONTEXT" ] && [ "$COMMENT_CONTEXT" != "{}" ]; then
# Build prompt with review context for pull_request_review_comment events
{
echo "User request: $COMMENT_WITHOUT_COMMAND"
echo ""
echo "Review context (file:line):"
echo '```json'
echo "$COMMENT_CONTEXT"
echo '```'
} > /tmp/prompt.txt
else
# Simple prompt for issue_comment events (backward compatible)
echo "$COMMENT_WITHOUT_COMMAND" > /tmp/prompt.txt
fi
- name: Run PI agent
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
id: run-agent
env:
SESSION_PATH: ${{ steps.session-check.outputs.path }}
run: |
RESPONSE=$(cat /tmp/prompt.txt | pi -p --session "$SESSION_PATH" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=$RESPONSE" >> $GITHUB_OUTPUT
fi
echo "response<<EOF" >> $GITHUB_ENV
echo "$RESPONSE" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Configure git user
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
run: |
git config user.name "PI Agent"
git config user.email "pi-agent@example.com"
- name: Check for file changes
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true'
id: check-changes
run: |
if [[ -n "$(git status --porcelain)" ]]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Add rocket reaction
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_ID: ${{ github.event.comment.id }}
GITHUB_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then
ENDPOINT="pulls/comments"
else
ENDPOINT="issues/comments"
fi
gh api "repos/$GITHUB_REPO/$ENDPOINT/$COMMENT_ID/reactions" \
-X POST \
-f content="rocket" || echo "Failed to add rocket reaction" >&2
- name: Commit and push changes
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
id: commit-push
run: |
git add -A
# Let the PI agent handle the commit
pi -p "$COMMIT_PROMPT" 2>&1
# Push the commit
git push || exit 1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_PROMPT: |
Review the staged changes and commit them with an appropriate conventional commit message.
Guidelines:
- Use conventional commit format: feat:, fix:, refactor:, docs:, test:, chore:, etc.
- Write a concise but descriptive message
- Focus on WHAT changed and WHY, not HOW
- Use git commit -m 'message' to commit the changes
- name: Define metadata prompt
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
id: define-metadata-prompt
run: |
cat << 'METADATA_EOF' > /tmp/metadata_prompt.txt
This is a pull request. Compare the current branch with main and generate an updated PR description.
Output ONLY a JSON object with no other text, no markdown fences, no explanation.
The JSON must have this schema:
{
"title": "A concise conventional-commit style title for the PR",
"description": "A detailed description of the changes including a '## Changes' section"
}
Correct output example:
{"title": "feat: update authentication", "description": "## Changes\n\n- Fixed token refresh issue\n- Added auto-logout on expiry\n\nRelated to #42, #43"}
Incorrect output example (DO NOT DO THIS):
```json
{"title": "feat: update auth", "description": "..."}
```
Instructions:
- Compare the current branch against main
- Examine the diff to understand what changed
- Generate a meaningful title that summarizes the actual changes
- Write a description that clearly explains what was done and why
- Include "Related to #N" for each related issue reference in the description
- Output ONLY valid JSON, nothing else
METADATA_EOF
echo "prompt_file=/tmp/metadata_prompt.txt" >> $GITHUB_OUTPUT
- name: Fetch issue references
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
id: fetch-issue-refs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
run: |
ISSUE_REFS=$(gh pr view $PR_NUMBER --json closedIssueReferences -q '.closedIssueReferences[] | "#\(.number)"' 2>/dev/null | tr '\n' ', ' | sed 's/, $//')
if [ -n "$ISSUE_REFS" ]; then
echo "has_refs=true" >> $GITHUB_OUTPUT
echo "refs=$ISSUE_REFS" >> $GITHUB_OUTPUT
else
echo "has_refs=false" >> $GITHUB_OUTPUT
echo "refs=" >> $GITHUB_OUTPUT
fi
- name: Generate PR metadata
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true'
id: generate-metadata
run: |
ISSUE_REFS="${{ steps.fetch-issue-refs.outputs.refs }}"
METADATA_PROMPT=$(cat "${{ steps.define-metadata-prompt.outputs.prompt_file }}")
if [ "${{ steps.fetch-issue-refs.outputs.has_refs }}" == "true" ]; then
METADATA_PROMPT_WITH_REFS="$METADATA_PROMPT"$'\n\n'"Related issues: $ISSUE_REFS"
else
METADATA_PROMPT_WITH_REFS="$METADATA_PROMPT"
fi
METADATA_RESPONSE=$(pi -p "$METADATA_PROMPT_WITH_REFS" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=$METADATA_RESPONSE" >> $GITHUB_OUTPUT
echo "Failed to generate PR metadata" >&2
exit 1
fi
DESCRIPTION=$(echo "$METADATA_RESPONSE" | jq -r '.description')
if [ -z "$DESCRIPTION" ] || [ "$DESCRIPTION" = "null" ]; then
echo "error=true" >> $GITHUB_OUTPUT
echo "error_message=Agent did not return valid JSON with description field" >> $GITHUB_OUTPUT
echo "Failed to parse PR description: agent did not return valid JSON" >&2
exit 1
fi
echo "description<<EOF" >> $GITHUB_OUTPUT
echo "$DESCRIPTION" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Update PR description
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.check-changes.outputs.has_changes == 'true' && steps.generate-metadata.outputs.error != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
DESCRIPTION: ${{ steps.generate-metadata.outputs.description }}
run: |
gh pr edit $PR_NUMBER --body "$DESCRIPTION"
- name: Post error comment
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.run-agent.outputs.error == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
ERROR_MSG: ${{ steps.run-agent.outputs.error_message }}
ARTIFACT_NAME: pi-session-pr-${{ steps.pr-data.outputs.number }}-${{ github.run_id }}
RUN_ID: ${{ github.run_id }}
run: |
ERROR_BODY="<!-- PI-BOT-COMMENT -->\n<!-- SESSION: artifact=${ARTIFACT_NAME},run=${RUN_ID} -->\n\n⚠️ **Error**\n\nAn error occurred:\n\`\`\`\n$ERROR_MSG\n\`\`\`"
gh api "repos/$GITHUB_REPO/issues/$PR_NUMBER/comments" -f body="$ERROR_BODY"
- name: Upload session artifact
if: (steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true') || cancelled()
id: upload-artifact
uses: actions/upload-artifact@v4
with:
name: pi-session-pr-${{ steps.pr-data.outputs.number }}-${{ github.run_id }}
path: ~/.pi/sessions
retention-days: 7
if-no-files-found: ignore
- name: Post response comment
if: steps.check-trigger.outputs.triggered == 'true' && steps.validate-config.outputs.config_valid == 'true' && steps.validate-state.outputs.valid == 'true' && steps.run-agent.outputs.error != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr-data.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
RESPONSE: ${{ env.response }}
ARTIFACT_NAME: pi-session-pr-${{ steps.pr-data.outputs.number }}-${{ github.run_id }}
RUN_ID: ${{ github.run_id }}
run: |
{
echo "<!-- PI-BOT-COMMENT -->"
echo "<!-- SESSION: artifact=${ARTIFACT_NAME},run=${RUN_ID} -->"
echo ""
printf '%s' "$RESPONSE"
} > /tmp/response_body.txt
gh api "repos/$GITHUB_REPO/issues/$PR_NUMBER/comments" \
-f body="$(cat /tmp/response_body.txt)"

PI GitHub Actions Workflows

AI-powered coding agent workflows for GitHub Issues and Pull Requests. Mention @pi in any issue or PR comment to trigger an AI agent that can analyze, code, and commit changes directly to your repository.

Overview

These workflows bring an async AI coding agent into your GitHub repository:

  • pi-issue-agent.yml - Responds to @pi mentions in issue comments, creates a branch, implements changes, and opens a pull request
  • pi-pr-agent.yml - Responds to @pi mentions in pull request comments and review threads, commits changes directly to the PR branch

Features

  • πŸ€– AI-powered responses - Uses Claude via PI agent to understand and implement requests
  • πŸ“ Issue-to-PR automation - Converts issue requests into implemented pull requests
  • πŸ” PR review support - Responds to code review comments with inline context
  • πŸ’Ύ Session persistence - Maintains conversation context across multiple interactions
  • πŸš€ Auto-commit - AI writes conventional commit messages and pushes changes
  • πŸ“Š PR metadata generation - AI generates meaningful PR titles and descriptions

Prerequisites

  1. GitHub repository with Actions enabled
  2. API key for a compatible AI provider

Setup

1. Copy Workflow Files

Create .github/workflows/ directory in your repo and add both workflow files:

mkdir -p .github/workflows
curl -o .github/workflows/pi-issue-agent.yml https://gist.githubusercontent.com/ybenitezf/a9b23f97aef78c9f36234bd20023375f/raw/pi-issue-agent.yml
curl -o .github/workflows/pi-pr-agent.yml https://gist.githubusercontent.com/ybenitezf/a9b23f97aef78c9f36234bd20023375f/raw/pi-pr-agent.yml

Or copy the files from this gist manually.

2. Configure Repository Variables

Go to Settings β†’ Secrets and variables β†’ Actions β†’ Variables and add:

PI_SETTINGS (JSON configuration) you need at least:

{
  "defaultProvider": "...",
  "defaultModel": "...",
  "defaultThinkingLevel": "...",
}

See pi-agent documentation, for example:

{
  "defaultProvider": "opencode-go",
  "defaultModel": "minimax-m2.5",
  "defaultThinkingLevel": "medium",
}

3. Configure Repository Secrets

Go to Settings β†’ Secrets and variables β†’ Actions β†’ Secrets and add:

PI_AUTH_JSON (Authentication credentials):

{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "google": { "type": "api_key", "key": "..." },
  "opencode": { "type": "api_key", "key": "..." },
  "opencode-go": { "type": "api_key", "key": "..." }
}

Note: You only need one, I recommend opencode-go subscription (https://opencode.ai/docs/go)

4. Set Permissions

The workflows require these permissions (already configured in the workflows):

  • actions: read - Download session artifacts
  • issues: write - Comment on issues
  • contents: write - Push commits
  • pull-requests: write - Create and update PRs

Usage

Prefer to use along with OpenSpec

After installing the workflows, initialize OpenSpec in the repository for pi:

openspec init

Select Pi.

In Issues

  1. Create an issue describing a feature request or bug
  2. Comment @pi followed by your request:
    @pi Implement a function to calculate fibonacci numbers with memoization
    
    or
    @pi use openspec explore skill
    
  3. The agent will:
    • Add an πŸ‘€ reaction to acknowledge
    • Create a branch: {issue-number}-pi-agent if changes are detected
    • Implement the requested changes
    • Commit with conventional commit message
    • Open a pull request
    • Comment with the PR link
    • If the agent don't modify the code then the agent response will be included as a comment in the issue.

In Pull Requests

  1. Open a pull request (created by PI or manually)
  2. Comment @pi in the PR discussion or review thread:
    @pi Fix the typo in the error message and add a test case for edge conditions
    
  3. For review comments (inline on code), the agent receives:
    • File path and line number
    • Diff context (the code being reviewed)
    • Your specific request

Session Continuity

The agent maintains conversation context within an issue/PR thread:

User: @pi Create a user authentication module
PI: βœ… I've created the auth module with login/logout methods...

User: @pi Add password hashing with bcrypt
PI: βœ… I've added bcrypt password hashing to the auth module...

User: @pi Now add JWT token generation
PI: βœ… I've integrated JWT token generation...

Sessions are stored as workflow artifacts and restored when you continue the conversation.

How It Works

Issue Agent Flow

Issue comment with @pi
       ↓
Validate PI_SETTINGS & PI_AUTH_JSON
       ↓
Check for existing branch {issue-num}-pi-agent
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Branch exists  β”‚  New branch       β”‚
β”‚  Checkout it    β”‚  Create from main β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       ↓
Build prompt (first run includes issue title/body)
       ↓
Run PI agent with session persistence
       ↓
Agent makes file changes
       ↓
Auto-commit with conventional message
       ↓
Create PR (new) or push (existing)
       ↓
Comment on issue with PR link

PR Agent Flow

PR comment with @pi
       ↓
Validate configuration
       ↓
Normalize PR data (handles both review comments & issue comments)
       ↓
Validate PR is open (skip if merged/closed)
       ↓
Checkout PR branch
       ↓
Build prompt (review comments include file:line context)
       ↓
Run PI agent with session persistence
       ↓
Agent makes file changes
       ↓
Auto-commit and push
       ↓
Update PR description with changes summary
       ↓
Comment response on PR

Trigger Patterns

The agent triggers on @pi with word boundaries:

Comment Triggers?
@pi fix this βœ… Yes
Please @pi update βœ… Yes
@pi: check this βœ… Yes
Check @pi-out ❌ No (part of word)
@PI fix this ❌ No (case-sensitive)

Security

  • Bot comments are filtered out (won't trigger itself)
  • Requires explicit @pi mention
  • Runs with ubuntu-latest in isolated environment
  • Session data stored as artifacts (7-day retention)
  • No code execution outside the Actions sandbox

Troubleshooting

"Required PI configuration not set"

  • Verify PI_SETTINGS variable and PI_AUTH_JSON secret are set
  • Check JSON syntax is valid

Agent doesn't respond

  • Ensure you're using lowercase @pi (case-sensitive)
  • Check the issue/PR is on github.com (not GHES)
  • Verify the comment isn't from a bot

Session lost / wrong context

  • Sessions are tied to artifacts; old runs may expire
  • Continue conversation before artifact retention (7 days)
  • Check workflow run logs for artifact download errors

PR creation fails

  • Ensure GITHUB_TOKEN has pull-requests: write permission
  • Check branch naming doesn't conflict with protected branches

Customization

Change the Trigger Word

Edit the check-trigger step in both workflows:

# Change @pi to your preferred trigger
if echo "$COMMENT_BODY" | grep -qP '(?<=^|[[:space:]])@agent(?=[[:space:]]|$|[[:punct:]])'; then

Credits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment