Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/actions/run-claude/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Composite Action for running Claude Code Action
#
# Wraps anthropics/claude-code-action with MCP server configuration.
# Template based on elastic/ai-github-actions base action.
#
# Usage:
# - uses: ./.github/actions/run-claude
# with:
# prompt: "Your prompt here"
# claude-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# github-token: ${{ steps.marvin-token.outputs.token }}
# allowed-tools: "Edit,Read,Write,Bash(*),mcp__github__add_issue_comment"
#
name: "Run Claude"
description: "Run Claude Code with MCP servers"
author: "FastMCP"

branding:
icon: "cpu"
color: "orange"

inputs:
prompt:
description: "Prompt to pass to Claude"
required: true

claude-oauth-token:
description: "Claude Code OAuth token for authentication"
required: true

github-token:
description: "GitHub token for Claude to operate with"
required: true

allowed-tools:
description: "Comma-separated list of allowed tools (e.g. Edit,Write,Bash(npm test))"
required: false
default: ""

model:
description: "Model to use for Claude"
required: false
default: "claude-opus-4-6"

allowed-bots:
description: "Allowed bot usernames, or '*' for all bots"
required: false
default: ""

track-progress:
description: "Whether Claude should track progress"
required: false
default: "true"

mcp-servers:
description: "MCP server configuration JSON"
required: false
default: '{"mcpServers":{"agents-md-generator":{"type":"http","url":"https://agents-md-generator.fastmcp.app/mcp"},"public-code-search":{"type":"http","url":"https://public-code-search.fastmcp.app/mcp"}}}'

trigger-phrase:
description: "Trigger phrase (for mention workflows)"
required: false
default: "/marvin"

outputs:
conclusion:
description: "The conclusion of the Claude Code run"
value: ${{ steps.claude.outputs.conclusion }}

runs:
using: "composite"
steps:
- name: Clean up stale Claude locks
shell: bash
run: rm -rf ~/.claude/.locks ~/.local/state/claude/locks || true

- name: Run Claude Code
id: claude
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
uses: anthropics/claude-code-action@v1
with:
github_token: ${{ inputs.github-token }}
claude_code_oauth_token: ${{ inputs.claude-oauth-token }}
bot_name: "Marvin Context Protocol"
trigger_phrase: ${{ inputs.trigger-phrase }}
allowed_bots: ${{ inputs.allowed-bots }}
track_progress: ${{ inputs.track-progress }}
prompt: ${{ inputs.prompt }}
claude_args: |
${{ (inputs.allowed-tools != '' || inputs.extra-allowed-tools != '') && format('--allowedTools {0}{1}', inputs.allowed-tools, inputs.extra-allowed-tools != '' && format(',{0}', inputs.extra-allowed-tools) || '') || '' }}
${{ inputs.mcp-servers != '' && format('--mcp-config ''{0}''', inputs.mcp-servers) || '' }}
--model ${{ inputs.model }}
settings: |
{"model": "${{ inputs.model }}"}
62 changes: 62 additions & 0 deletions .github/scripts/mention/gh-get-review-threads.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail

# Get PR review threads with comments via GitHub GraphQL API
#
# Usage:
# gh-get-review-threads.sh [FILTER]
#
# Arguments:
# FILTER - Optional: filter for unresolved threads from specific author
#
# Environment (set by composite action):
# MENTION_REPO - Repository (owner/repo format)
# MENTION_PR_NUMBER - Pull request number
# GITHUB_TOKEN - GitHub API token
#
# Output:
# JSON array of review threads with nested comments

# Parse OWNER and REPO from MENTION_REPO
REPO_FULL="${MENTION_REPO:?MENTION_REPO environment variable is required}"
OWNER="${REPO_FULL%/*}"
REPO="${REPO_FULL#*/}"
PR_NUMBER="${MENTION_PR_NUMBER:?MENTION_PR_NUMBER environment variable is required}"
FILTER="${1:-}"

gh api graphql -f query='
query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
path
line
comments(first: 50) {
nodes {
id
body
author { login }
createdAt
}
}
}
}
}
}
}' -F owner="$OWNER" \
-F repo="$REPO" \
-F prNumber="$PR_NUMBER" \
--jq '.data.repository.pullRequest.reviewThreads.nodes' | \
if [ -n "$FILTER" ]; then
jq --arg author "$FILTER" '
map(select(
.isResolved == false and
.comments.nodes | any(.author.login == $author)
Comment on lines +57 to +58
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parenthesize the jq author filter before piping to any

When gh-get-review-threads.sh is called with a filter argument (as documented in the PR review guidance), this jq expression fails because | binds after and, so it effectively pipes a boolean into any(...) and exits with Cannot iterate over boolean. Since the script runs with set -euo pipefail, reviewer-specific thread filtering currently aborts instead of returning matching unresolved threads.

Useful? React with 👍 / 👎.

))'
else
cat
fi
61 changes: 61 additions & 0 deletions .github/scripts/mention/gh-resolve-review-thread.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail

# Resolve a GitHub PR review thread, optionally posting a comment first
#
# Usage:
# gh-resolve-review-thread.sh THREAD_ID [COMMENT]
#
# Arguments:
# THREAD_ID - The GraphQL node ID of the review thread to resolve
# COMMENT - Optional: Comment body to post before resolving
#
# Environment (set by composite action):
# MENTION_REPO - Repository (owner/repo format)
# MENTION_PR_NUMBER - Pull request number
# GITHUB_TOKEN - GitHub API token
#
# Behavior:
# 1. If COMMENT is provided, posts it as a reply to the thread
# 2. Resolves the thread

# Validate required environment variables
: "${MENTION_REPO:?MENTION_REPO environment variable is required}"
: "${MENTION_PR_NUMBER:?MENTION_PR_NUMBER environment variable is required}"
THREAD_ID="${1:?Thread ID required}"
COMMENT="${2:-}"

# Step 1: Post comment if provided
if [ -n "$COMMENT" ]; then
echo "Posting comment to thread..." >&2
COMMENT_RESULT=$(gh api graphql -f query='
mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $threadId,
body: $body
}) {
comment {
id
}
}
}' -f threadId="$THREAD_ID" -f body="$COMMENT")
if echo "$COMMENT_RESULT" | jq -e '.errors' > /dev/null 2>&1; then
echo "Error posting comment: $COMMENT_RESULT" >&2
exit 1
fi
fi

# Step 2: Resolve the thread
echo "Resolving thread..." >&2
RESOLVE_RESULT=$(gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread {
id
isResolved
}
}
}' -f threadId="$THREAD_ID" --jq '.data.resolveReviewThread.thread')

echo "$RESOLVE_RESULT"
echo "✓ Thread resolved" >&2
Loading