Feedback: https://docs.netwrix.com/docs/dataclassification/5_7/introduction/requirements/hardwarerequirements #284
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Issue Processor | |
| on: | |
| issues: | |
| types: [opened, edited] | |
| issue_comment: | |
| types: [created, edited] | |
| jobs: | |
| process-issue: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| sparse-checkout: | | |
| .github | |
| .claude | |
| CODE_OF_CONDUCT.md | |
| sparse-checkout-cone-mode: false | |
| - name: Step 1 — Security vulnerability screening | |
| id: security-screen | |
| uses: anthropics/claude-code-action@24492741e0ccfdef4c1d19da8e11e0f373d07494 # v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| allowed_non_write_users: '*' | |
| prompt: | | |
| Call the security screening skill: | |
| /identify-security-vuln-discussion | |
| Context: | |
| - REPO: ${{ github.repository }} | |
| - ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - ISSUE_TITLE: ${{ github.event.issue.title }} | |
| - ISSUE_BODY: Fetch the latest body with: gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json body --jq .body | |
| - ISSUE_AUTHOR: ${{ github.event.issue.user.login }} | |
| Note: This skill checks both the issue body AND all comments for security vulnerabilities. | |
| This protects against bypass attempts where someone edits a clean issue to add vulnerability info. | |
| claude_args: '--allowedTools "Bash(gh:*),Read,Grep,Glob,Skill(identify-security-vuln-discussion)"' | |
| - name: Check if issue is still open | |
| id: check-state | |
| run: | | |
| STATE=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json state --jq .state) | |
| echo "issue_state=$STATE" >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Step 2 — Code of conduct check | |
| if: steps.check-state.outputs.issue_state == 'OPEN' | |
| id: conduct-check | |
| uses: anthropics/claude-code-action@24492741e0ccfdef4c1d19da8e11e0f373d07494 # v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| allowed_non_write_users: '*' | |
| prompt: | | |
| Call the code of conduct check skill: | |
| /code-of-conduct-check | |
| Context: | |
| - REPO: ${{ github.repository }} | |
| - ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - ISSUE_TITLE: ${{ github.event.issue.title }} | |
| - ISSUE_BODY: Fetch the latest body with: gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json body --jq .body | |
| - ISSUE_AUTHOR: ${{ github.event.issue.user.login }} | |
| Note: This skill checks both the issue body AND all comments for code of conduct violations. | |
| This ensures community guidelines are enforced across all issue content. | |
| claude_args: '--allowedTools "Bash(gh:*),Read,Grep,Glob,Skill(code-of-conduct-check)"' | |
| - name: Step 3 — Assign label | |
| # Only run label assignment for issue events (not comments) and only if issue is still open | |
| if: steps.check-state.outputs.issue_state == 'OPEN' && github.event_name == 'issues' | |
| id: assign-label | |
| uses: anthropics/claude-code-action@24492741e0ccfdef4c1d19da8e11e0f373d07494 # v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| allowed_non_write_users: '*' | |
| prompt: | | |
| Call the label assignment skill: | |
| /assign-label | |
| Context: | |
| - REPO: ${{ github.repository }} | |
| - ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - ISSUE_TITLE: ${{ github.event.issue.title }} | |
| - ISSUE_BODY: Fetch the latest body with: gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json body --jq .body | |
| - ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }} | |
| Note: The skill should preserve template-assigned labels and add complementary labels. | |
| Issue body may have been modified by previous steps. | |
| claude_args: '--allowedTools "Bash(gh:*),Skill(assign-label)"' | |
| - name: Step 4 — Notify codeowners | |
| if: steps.check-state.outputs.issue_state == 'OPEN' && github.event_name == 'issues' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Fetch current labels from the issue (post-Step 3) | |
| LABELS=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json labels --jq '.labels[].name') | |
| if [ -z "$LABELS" ]; then | |
| echo "No labels found — skipping codeowner notification" | |
| exit 0 | |
| fi | |
| # Skip codeowner notification for KB PR review tracking issues | |
| if echo "$LABELS" | grep -q "kb/review"; then | |
| echo "Issue has kb/review label — skipping codeowner notification" | |
| exit 0 | |
| fi | |
| # Read the mapping file | |
| MAPPING=$(cat .github/label-codeowners.json) | |
| # Collect matched teams (deduplicated) | |
| TEAMS="" | |
| while IFS= read -r label; do | |
| team=$(echo "$MAPPING" | jq -r --arg l "$label" '.[$l] // empty') | |
| if [ -n "$team" ]; then | |
| # Deduplicate (identity-recovery and another label could map to same team) | |
| if ! echo "$TEAMS" | grep -qF "$team"; then | |
| TEAMS="${TEAMS:+$TEAMS }$team" | |
| fi | |
| fi | |
| done <<< "$LABELS" | |
| if [ -z "$TEAMS" ]; then | |
| echo "No product labels matched — skipping codeowner notification" | |
| exit 0 | |
| fi | |
| echo "Notifying teams: $TEAMS" | |
| # Post a single comment tagging all matched teams | |
| COMMENT="Notifying codeowners: ${TEAMS}" | |
| COMMENT="${COMMENT}"$'\n\n'"Please review this issue when you have a chance." | |
| gh issue comment ${{ github.event.issue.number }} \ | |
| --repo ${{ github.repository }} \ | |
| --body "$COMMENT" | |
| content-fix: | |
| needs: process-issue | |
| if: >- | |
| github.event_name == 'issues' && | |
| github.event.action == 'opened' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| steps: | |
| - name: Check issue state and labels | |
| id: check-issue | |
| run: | | |
| ISSUE_DATA=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json state,labels) | |
| STATE=$(echo "$ISSUE_DATA" | jq -r '.state') | |
| HAS_CONTENT_FIX=$(echo "$ISSUE_DATA" | jq '[.labels[].name] | any(. == "content:fix")') | |
| echo "issue_state=$STATE" >> "$GITHUB_OUTPUT" | |
| echo "has_content_fix=$HAS_CONTENT_FIX" >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Checkout repository | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| token: ${{ secrets.ISSUE_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Configure git identity | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Run content-fix skill | |
| id: content-fix | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| uses: anthropics/claude-code-action@24492741e0ccfdef4c1d19da8e11e0f373d07494 # v1 | |
| env: | |
| REPO: ${{ github.repository }} | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| show_full_output: true | |
| prompt: | | |
| /content-fix ${{ github.event.issue.number }} | |
| Context: | |
| - REPO: ${{ github.repository }} | |
| - ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - ISSUE_TITLE: ${{ github.event.issue.title }} | |
| claude_args: '--model claude-sonnet-4-6 --max-turns 50 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,Skill(content-fix)"' | |
| - name: Create PR and comment on issue | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.ISSUE_TOKEN }} | |
| run: | | |
| OUTPUT_FILE="/home/runner/work/_temp/claude-execution-output.json" | |
| # Extract the result text from the JSONL output file | |
| # The file has multi-line JSON objects, so use jq --slurp to handle them | |
| CLAUDE_OUTPUT=$(jq -rs '[.[] | select(.result != null)] | last | .result // empty' "$OUTPUT_FILE" 2>/dev/null || echo "") | |
| # Fallback: grep the raw file for BRANCH_NAME if jq extraction failed | |
| if [ -z "$CLAUDE_OUTPUT" ]; then | |
| echo "jq extraction failed, falling back to grep" | |
| CLAUDE_OUTPUT=$(grep -oP 'BRANCH_NAME=\K[^\s"\\]+' "$OUTPUT_FILE" 2>/dev/null | tail -1 || echo "") | |
| if [ -n "$CLAUDE_OUTPUT" ]; then | |
| BRANCH_NAME="$CLAUDE_OUTPUT" | |
| fi | |
| fi | |
| echo "Claude output (first 200 chars): ${CLAUDE_OUTPUT:0:200}" | |
| # Extract branch name from the result text | |
| if [ -z "${BRANCH_NAME:-}" ]; then | |
| BRANCH_NAME=$(echo "$CLAUDE_OUTPUT" | grep -oP 'BRANCH_NAME=\K\S+' | head -1) | |
| fi | |
| if [ -z "$BRANCH_NAME" ]; then | |
| echo "No branch name found in Claude output — skill likely asked a clarifying question" | |
| exit 0 | |
| fi | |
| echo "Found branch: $BRANCH_NAME" | |
| # Check if branch exists on remote | |
| if ! git ls-remote --exit-code origin "$BRANCH_NAME" > /dev/null 2>&1; then | |
| echo "Branch $BRANCH_NAME not found on remote — skipping PR creation" | |
| exit 0 | |
| fi | |
| # Extract description from Claude's output (everything before BRANCH_NAME line) | |
| DESCRIPTION=$(echo "$CLAUDE_OUTPUT" | sed '/BRANCH_NAME=/d' | sed '/^$/d') | |
| ISSUE_NUM=${{ github.event.issue.number }} | |
| PR_TITLE="docs: fix #${ISSUE_NUM} — $(echo "$BRANCH_NAME" | sed 's|fix/issue-[0-9]*-||' | tr '-' ' ')" | |
| # Create PR using ISSUE_TOKEN so it triggers downstream workflows | |
| PR_URL=$(gh pr create \ | |
| --repo ${{ github.repository }} \ | |
| --base dev \ | |
| --head "$BRANCH_NAME" \ | |
| --title "$PR_TITLE" \ | |
| --body "Closes #${ISSUE_NUM} | |
| ## What changed | |
| ${DESCRIPTION}") | |
| echo "Created PR: $PR_URL" | |
| # Comment on the issue | |
| PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') | |
| gh issue comment "$ISSUE_NUM" \ | |
| --repo ${{ github.repository }} \ | |
| --body "Fix submitted in PR #${PR_NUMBER}." | |
| content-fix-followup: | |
| needs: process-issue | |
| if: >- | |
| github.event_name == 'issue_comment' && | |
| !github.event.issue.pull_request && | |
| contains(github.event.comment.body, '@claude') && | |
| !startsWith(github.event.comment.user.login, 'github-actions') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| steps: | |
| - name: Check issue state and labels | |
| id: check-issue | |
| run: | | |
| ISSUE_DATA=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json state,labels) | |
| STATE=$(echo "$ISSUE_DATA" | jq -r '.state') | |
| HAS_CONTENT_FIX=$(echo "$ISSUE_DATA" | jq '[.labels[].name] | any(. == "content:fix")') | |
| echo "issue_state=$STATE" >> "$GITHUB_OUTPUT" | |
| echo "has_content_fix=$HAS_CONTENT_FIX" >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: React to comment | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \ | |
| -f content="eyes" | |
| - name: Checkout repository | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| token: ${{ secrets.ISSUE_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Configure git identity | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Run content-fix skill | |
| id: content-fix-followup | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| uses: anthropics/claude-code-action@24492741e0ccfdef4c1d19da8e11e0f373d07494 # v1 | |
| env: | |
| REPO: ${{ github.repository }} | |
| COMMENT_BODY: ${{ github.event.comment.body }} | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| show_full_output: true | |
| prompt: | | |
| /content-fix ${{ github.event.issue.number }} | |
| Context: | |
| - REPO: ${{ github.repository }} | |
| - ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - ISSUE_TITLE: ${{ github.event.issue.title }} | |
| - TRIGGERING_COMMENT: $COMMENT_BODY | |
| This is a follow-up invocation. A user replied with @claude on a content_fix issue. | |
| Read the full issue body AND all comments to understand the complete context. | |
| claude_args: '--model claude-sonnet-4-6 --max-turns 50 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,Skill(content-fix)"' | |
| - name: Create PR and comment on issue | |
| if: steps.check-issue.outputs.issue_state == 'OPEN' && steps.check-issue.outputs.has_content_fix == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.ISSUE_TOKEN }} | |
| run: | | |
| OUTPUT_FILE="/home/runner/work/_temp/claude-execution-output.json" | |
| # Extract the result text from the JSONL output file | |
| # The file has multi-line JSON objects, so use jq --slurp to handle them | |
| CLAUDE_OUTPUT=$(jq -rs '[.[] | select(.result != null)] | last | .result // empty' "$OUTPUT_FILE" 2>/dev/null || echo "") | |
| # Fallback: grep the raw file for BRANCH_NAME if jq extraction failed | |
| if [ -z "$CLAUDE_OUTPUT" ]; then | |
| echo "jq extraction failed, falling back to grep" | |
| CLAUDE_OUTPUT=$(grep -oP 'BRANCH_NAME=\K[^\s"\\]+' "$OUTPUT_FILE" 2>/dev/null | tail -1 || echo "") | |
| if [ -n "$CLAUDE_OUTPUT" ]; then | |
| BRANCH_NAME="$CLAUDE_OUTPUT" | |
| fi | |
| fi | |
| echo "Claude output (first 200 chars): ${CLAUDE_OUTPUT:0:200}" | |
| # Extract branch name from the result text | |
| if [ -z "${BRANCH_NAME:-}" ]; then | |
| BRANCH_NAME=$(echo "$CLAUDE_OUTPUT" | grep -oP 'BRANCH_NAME=\K\S+' | head -1) | |
| fi | |
| if [ -z "$BRANCH_NAME" ]; then | |
| echo "No branch name found in Claude output — skill likely asked a clarifying question" | |
| exit 0 | |
| fi | |
| echo "Found branch: $BRANCH_NAME" | |
| # Check if branch exists on remote | |
| if ! git ls-remote --exit-code origin "$BRANCH_NAME" > /dev/null 2>&1; then | |
| echo "Branch $BRANCH_NAME not found on remote — skipping PR creation" | |
| exit 0 | |
| fi | |
| # Extract description from Claude's output (everything before BRANCH_NAME line) | |
| DESCRIPTION=$(echo "$CLAUDE_OUTPUT" | sed '/BRANCH_NAME=/d' | sed '/^$/d') | |
| ISSUE_NUM=${{ github.event.issue.number }} | |
| PR_TITLE="docs: fix #${ISSUE_NUM} — $(echo "$BRANCH_NAME" | sed 's|fix/issue-[0-9]*-||' | tr '-' ' ')" | |
| # Create PR using ISSUE_TOKEN so it triggers downstream workflows | |
| PR_URL=$(gh pr create \ | |
| --repo ${{ github.repository }} \ | |
| --base dev \ | |
| --head "$BRANCH_NAME" \ | |
| --title "$PR_TITLE" \ | |
| --body "Closes #${ISSUE_NUM} | |
| ## What changed | |
| ${DESCRIPTION}") | |
| echo "Created PR: $PR_URL" | |
| # Comment on the issue | |
| PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') | |
| gh issue comment "$ISSUE_NUM" \ | |
| --repo ${{ github.repository }} \ | |
| --body "Fix submitted in PR #${PR_NUMBER}." |