From 76de8a48fcd644600947af159dcd98afc7da8713 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 13 Aug 2025 20:26:17 +0000 Subject: [PATCH 01/17] chore: bump Claude Code version to 1.0.79 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index c945d16..71d8261 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.77 + bun install -g @anthropic-ai/claude-code@1.0.79 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index b49fdfd..7e2adb1 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.77 + run: bun install -g @anthropic-ai/claude-code@1.0.79 - name: Run Claude Code Action shell: bash From 2b67ac084bc315ef958e0d346fcd768f29c75483 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 13 Aug 2025 20:33:11 +0000 Subject: [PATCH 02/17] chore: bump Claude Code version to 1.0.77 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 71d8261..c945d16 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.79 + bun install -g @anthropic-ai/claude-code@1.0.77 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index 7e2adb1..b49fdfd 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.79 + run: bun install -g @anthropic-ai/claude-code@1.0.77 - name: Run Claude Code Action shell: bash From 449c6791bd850c2f7bed8de8cf9d557f6663e1ec Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 13 Aug 2025 21:17:49 +0000 Subject: [PATCH 03/17] chore: bump Claude Code version to 1.0.80 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index c945d16..4b2c634 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.77 + bun install -g @anthropic-ai/claude-code@1.0.80 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index b49fdfd..92d75b0 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.77 + run: bun install -g @anthropic-ai/claude-code@1.0.80 - name: Run Claude Code Action shell: bash From c34e066a3bbc68efb4f903aee2e9d864c525bcc9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 14 Aug 2025 17:00:23 +0000 Subject: [PATCH 04/17] chore: bump Claude Code version to 1.0.81 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 4b2c634..8aa9a9b 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.80 + bun install -g @anthropic-ai/claude-code@1.0.81 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index 92d75b0..6a42bbe 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.80 + run: bun install -g @anthropic-ai/claude-code@1.0.81 - name: Run Claude Code Action shell: bash From 0b138d9d491aac05f09b58c13bcdf46888a7d08c Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Thu, 14 Aug 2025 16:42:49 -0700 Subject: [PATCH 05/17] Update token.ts copy (#450) --- src/github/token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/token.ts b/src/github/token.ts index 234070c..6c83c9b 100644 --- a/src/github/token.ts +++ b/src/github/token.ts @@ -78,7 +78,7 @@ export async function setupGitHubToken(): Promise { return appToken; } catch (error) { core.setFailed( - `Failed to setup GitHub token: ${error}.\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`, + `Failed to setup GitHub token: ${error}\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`, ); process.exit(1); } From 432c7cc889ce43c1dcc060d18cb44dd9d7b2217b Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Thu, 14 Aug 2025 19:09:58 -0700 Subject: [PATCH 06/17] update example workflow (#451) --- examples/claude.yml | 57 ++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/examples/claude.yml b/examples/claude.yml index 53c207a..d1cc5bb 100644 --- a/examples/claude.yml +++ b/examples/claude.yml @@ -1,4 +1,4 @@ -name: Claude PR Assistant +name: Claude Code on: issue_comment: @@ -11,38 +11,53 @@ on: types: [submitted] jobs: - claude-code-action: + claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: - contents: read - pull-requests: read - issues: read + contents: write + pull-requests: write + issues: write id-token: write + actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - - name: Run Claude PR Action + - name: Run Claude Code + id: claude uses: anthropics/claude-code-action@beta with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - # Or use OAuth token instead: - # claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - timeout_minutes: "60" - # mode: tag # Default: responds to @claude mentions - # Optional: Restrict network access to specific domains only - # experimental_allowed_domains: | - # .anthropic.com - # .github.com - # api.github.com - # .githubusercontent.com - # bun.sh - # registry.npmjs.org - # .blob.core.windows.net + anthropic_api_key: \${{ secrets.ANTHROPIC_API_KEY }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) + # model: "claude-opus-4-1-20250805" + + # Optional: Customize the trigger phrase (default: @claude) + # trigger_phrase: "/claude" + + # Optional: Trigger when specific user is assigned to an issue + # assignee_trigger: "claude-bot" + + # Optional: Allow Claude to run specific commands + # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" + + # Optional: Add custom instructions for Claude to customize its behavior for your project + # custom_instructions: | + # Follow our coding standards + # Ensure all new code has tests + # Use TypeScript for new files + + # Optional: Custom environment variables for Claude + # claude_env: | + # NODE_ENV: test From ae66eb6a644f9c34886cd7d4d5a7516e9b472b85 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 15 Aug 2025 09:11:02 -0700 Subject: [PATCH 07/17] Switch to curl-based Claude Code installation (#452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace bun install with official install script for more reliable installation across different environments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 8aa9a9b..f67993f 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - bun install -g @anthropic-ai/claude-code@1.0.81 + curl -fsSL https://claude.ai/install.sh | bash -s 1.0.81 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index 6a42bbe..4facaa5 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: bun install -g @anthropic-ai/claude-code@1.0.81 + run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.81 - name: Run Claude Code Action shell: bash From a1507aefdc04f00f278407dba905d158ac62399e Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 15 Aug 2025 13:04:52 -0700 Subject: [PATCH 08/17] Add GitHub token redaction to comment tools (#453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add GitHub token redaction to update_claude_comment tool - Add redactGitHubTokens() function to sanitizer.ts that detects and redacts all GitHub token formats (ghp_, gho_, ghs_, ghr_, github_pat_) - Update sanitizeContent() to include token redaction in the sanitization pipeline - Apply sanitization to comment body in github-comment-server.ts before updating comments - Add comprehensive tests covering all token formats, edge cases, and integration scenarios - Prevents accidental exposure of GitHub tokens in PR/issue comments while preserving existing functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Add GitHub token redaction to inline comment server - Apply sanitizeContent() to comment body in github-inline-comment-server.ts before creating inline PR comments - Ensures consistency in token redaction across all comment creation tools - Prevents GitHub tokens from being exposed in inline PR review comments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- src/github/utils/sanitizer.ts | 35 ++++++++ src/mcp/github-comment-server.ts | 5 +- src/mcp/github-inline-comment-server.ts | 6 +- test/sanitizer.test.ts | 104 ++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 2 deletions(-) diff --git a/src/github/utils/sanitizer.ts b/src/github/utils/sanitizer.ts index ef5d3cc..83ee096 100644 --- a/src/github/utils/sanitizer.ts +++ b/src/github/utils/sanitizer.ts @@ -58,6 +58,41 @@ export function sanitizeContent(content: string): string { content = stripMarkdownLinkTitles(content); content = stripHiddenAttributes(content); content = normalizeHtmlEntities(content); + content = redactGitHubTokens(content); + return content; +} + +export function redactGitHubTokens(content: string): string { + // GitHub Personal Access Tokens (classic): ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars) + content = content.replace( + /\bghp_[A-Za-z0-9]{36}\b/g, + "[REDACTED_GITHUB_TOKEN]", + ); + + // GitHub OAuth tokens: gho_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars) + content = content.replace( + /\bgho_[A-Za-z0-9]{36}\b/g, + "[REDACTED_GITHUB_TOKEN]", + ); + + // GitHub installation tokens: ghs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars) + content = content.replace( + /\bghs_[A-Za-z0-9]{36}\b/g, + "[REDACTED_GITHUB_TOKEN]", + ); + + // GitHub refresh tokens: ghr_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars) + content = content.replace( + /\bghr_[A-Za-z0-9]{36}\b/g, + "[REDACTED_GITHUB_TOKEN]", + ); + + // GitHub fine-grained personal access tokens: github_pat_XXXXXXXXXX (up to 255 chars) + content = content.replace( + /\bgithub_pat_[A-Za-z0-9_]{11,221}\b/g, + "[REDACTED_GITHUB_TOKEN]", + ); + return content; } diff --git a/src/mcp/github-comment-server.ts b/src/mcp/github-comment-server.ts index 18ab6a2..ef6728c 100644 --- a/src/mcp/github-comment-server.ts +++ b/src/mcp/github-comment-server.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { GITHUB_API_URL } from "../github/api/config"; import { Octokit } from "@octokit/rest"; import { updateClaudeComment } from "../github/operations/comments/update-claude-comment"; +import { sanitizeContent } from "../github/utils/sanitizer"; // Get repository information from environment variables const REPO_OWNER = process.env.REPO_OWNER; @@ -54,11 +55,13 @@ server.tool( const isPullRequestReviewComment = eventName === "pull_request_review_comment"; + const sanitizedBody = sanitizeContent(body); + const result = await updateClaudeComment(octokit, { owner, repo, commentId, - body, + body: sanitizedBody, isPullRequestReviewComment, }); diff --git a/src/mcp/github-inline-comment-server.ts b/src/mcp/github-inline-comment-server.ts index a432466..703cda2 100644 --- a/src/mcp/github-inline-comment-server.ts +++ b/src/mcp/github-inline-comment-server.ts @@ -3,6 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { createOctokit } from "../github/api/client"; +import { sanitizeContent } from "../github/utils/sanitizer"; // Get repository and PR information from environment variables const REPO_OWNER = process.env.REPO_OWNER; @@ -81,6 +82,9 @@ server.tool( const octokit = createOctokit(githubToken).rest; + // Sanitize the comment body to remove any potential GitHub tokens + const sanitizedBody = sanitizeContent(body); + // Validate that either line or both startLine and line are provided if (!line && !startLine) { throw new Error( @@ -104,7 +108,7 @@ server.tool( owner, repo, pull_number, - body, + body: sanitizedBody, path, side: side || "RIGHT", commit_id: commit_id || pr.data.head.sha, diff --git a/test/sanitizer.test.ts b/test/sanitizer.test.ts index f28366a..a89353b 100644 --- a/test/sanitizer.test.ts +++ b/test/sanitizer.test.ts @@ -7,6 +7,7 @@ import { normalizeHtmlEntities, sanitizeContent, stripHtmlComments, + redactGitHubTokens, } from "../src/github/utils/sanitizer"; describe("stripInvisibleCharacters", () => { @@ -242,6 +243,109 @@ describe("sanitizeContent", () => { }); }); +describe("redactGitHubTokens", () => { + it("should redact personal access tokens (ghp_)", () => { + const token = "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"; + expect(redactGitHubTokens(`Token: ${token}`)).toBe( + "Token: [REDACTED_GITHUB_TOKEN]", + ); + expect(redactGitHubTokens(`Here's a token: ${token} in text`)).toBe( + "Here's a token: [REDACTED_GITHUB_TOKEN] in text", + ); + }); + + it("should redact OAuth tokens (gho_)", () => { + const token = "gho_16C7e42F292c6912E7710c838347Ae178B4a"; + expect(redactGitHubTokens(`OAuth: ${token}`)).toBe( + "OAuth: [REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should redact installation tokens (ghs_)", () => { + const token = "ghs_xz7yzju2SZjGPa0dUNMAx0SH4xDOCS31LXQW"; + expect(redactGitHubTokens(`Install token: ${token}`)).toBe( + "Install token: [REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should redact refresh tokens (ghr_)", () => { + const token = "ghr_1B4a2e77838347a253e56d7b5253e7d11667"; + expect(redactGitHubTokens(`Refresh: ${token}`)).toBe( + "Refresh: [REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should redact fine-grained tokens (github_pat_)", () => { + const token = + "github_pat_11ABCDEFG0example5of9_2nVwvsylpmOLboQwTPTLewDcE621dQ0AAaBBCCDDEEFFHH"; + expect(redactGitHubTokens(`Fine-grained: ${token}`)).toBe( + "Fine-grained: [REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should handle tokens in code blocks", () => { + const content = `\`\`\`bash +export GITHUB_TOKEN=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 +\`\`\``; + const expected = `\`\`\`bash +export GITHUB_TOKEN=[REDACTED_GITHUB_TOKEN] +\`\`\``; + expect(redactGitHubTokens(content)).toBe(expected); + }); + + it("should handle multiple tokens in one text", () => { + const content = + "Token 1: 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 and token 2: gho_16C7e42F292c6912E7710c838347Ae178B4a"; + expect(redactGitHubTokens(content)).toBe( + "Token 1: [REDACTED_GITHUB_TOKEN] and token 2: [REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should handle tokens in URLs", () => { + const content = + "https://api.github.com/user?access_token=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"; + expect(redactGitHubTokens(content)).toBe( + "https://api.github.com/user?access_token=[REDACTED_GITHUB_TOKEN]", + ); + }); + + it("should not redact partial matches or invalid tokens", () => { + const content = + "This is not a token: ghp_short or gho_toolong1234567890123456789012345678901234567890"; + expect(redactGitHubTokens(content)).toBe(content); + }); + + it("should preserve normal text", () => { + const content = "Normal text with no tokens"; + expect(redactGitHubTokens(content)).toBe(content); + }); + + it("should handle edge cases", () => { + expect(redactGitHubTokens("")).toBe(""); + expect(redactGitHubTokens("ghp_")).toBe("ghp_"); + expect(redactGitHubTokens("github_pat_short")).toBe("github_pat_short"); + }); +}); + +describe("sanitizeContent with token redaction", () => { + it("should redact tokens as part of full sanitization", () => { + const content = ` + + Here's some text with a token: gho_16C7e42F292c6912E7710c838347Ae178B4a + And invisible chars: test\u200Btoken + `; + + const sanitized = sanitizeContent(content); + + expect(sanitized).not.toContain("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"); + expect(sanitized).not.toContain("gho_16C7e42F292c6912E7710c838347Ae178B4a"); + expect(sanitized).not.toContain("World")).toBe( From f562ed53e29d9f5635f005cc704d5a03b6300cc3 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 15 Aug 2025 13:33:01 -0700 Subject: [PATCH 09/17] fix typo in example (#454) --- examples/claude.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/claude.yml b/examples/claude.yml index d1cc5bb..f2cf262 100644 --- a/examples/claude.yml +++ b/examples/claude.yml @@ -34,7 +34,7 @@ jobs: id: claude uses: anthropics/claude-code-action@beta with: - anthropic_api_key: \${{ secrets.ANTHROPIC_API_KEY }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | From 78b07473f50218c6494719ef164ed1ebd31da25c Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 16 Aug 2025 00:11:18 +0000 Subject: [PATCH 10/17] chore: bump Claude Code version to 1.0.83 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index f67993f..626e086 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - curl -fsSL https://claude.ai/install.sh | bash -s 1.0.81 + curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' diff --git a/base-action/action.yml b/base-action/action.yml index 4facaa5..20c632d 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.81 + run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 - name: Run Claude Code Action shell: bash From 02e9ed31816be8cb8d521a26f4ba981e5c89ac5e Mon Sep 17 00:00:00 2001 From: Hironori Yamamoto Date: Mon, 18 Aug 2025 13:06:17 +0900 Subject: [PATCH 11/17] fix: add Claude Code binary to GitHub Actions PATH (#455) --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index 626e086..4d1b330 100644 --- a/action.yml +++ b/action.yml @@ -178,6 +178,7 @@ runs: cd - # Install Claude Code globally curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 + echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' From e89411bb6f540b448a41d4a997c7c8dd9376dafb Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 18 Aug 2025 10:50:15 -0700 Subject: [PATCH 12/17] feat: skip action gracefully for workflow validation errors (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: skip action gracefully for workflow validation errors Handle workflow_not_found_on_default_branch and workflow_content_mismatch errors by skipping the action with a warning instead of failing. This improves user experience when adding Claude Code workflows to new repositories or making workflow changes in PRs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Update src/github/token.ts --------- Co-authored-by: Claude --- action.yml | 2 +- src/github/token.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 4d1b330..d1f8434 100644 --- a/action.yml +++ b/action.yml @@ -286,7 +286,7 @@ runs: fi - name: Revoke app token - if: always() && inputs.github_token == '' + if: always() && inputs.github_token == '' && steps.prepare.outputs.skipped_due_to_workflow_validation_mismatch != 'true' shell: bash run: | curl -L \ diff --git a/src/github/token.ts b/src/github/token.ts index 6c83c9b..da30285 100644 --- a/src/github/token.ts +++ b/src/github/token.ts @@ -31,8 +31,33 @@ async function exchangeForAppToken(oidcToken: string): Promise { const responseJson = (await response.json()) as { error?: { message?: string; + details?: { + error_code?: string; + }; }; + type?: string; + message?: string; }; + + // Check for specific workflow validation error codes that should skip the action + const errorCode = responseJson.error?.details?.error_code; + + if ( + errorCode === "workflow_not_found_on_default_branch" || + errorCode === "workflow_content_mismatch" + ) { + const message = + responseJson.message ?? + responseJson.error?.message ?? + "Workflow validation failed"; + core.warning(`Skipping action due to workflow validation: ${message}`); + console.log( + "Action skipped due to workflow validation error. This is expected when adding Claude Code workflows to new repositories or on PRs with workflow changes.", + ); + core.setOutput("skipped_due_to_workflow_validation_mismatch", "true"); + process.exit(0); + } + console.error( `App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`, ); @@ -77,6 +102,7 @@ export async function setupGitHubToken(): Promise { core.setOutput("GITHUB_TOKEN", appToken); return appToken; } catch (error) { + // Only set failed if we get here - workflow validation errors will exit(0) before this core.setFailed( `Failed to setup GitHub token: ${error}\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`, ); From f05d669d5fcfaa9e5372eb0eb6f8aaaad32ce383 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 18 Aug 2025 10:51:57 -0700 Subject: [PATCH 13/17] fix: prevent undefined directory creation when RUNNER_TEMP is not set (#461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running tests locally, process.env.RUNNER_TEMP is undefined, causing the code to literally create "undefined/claude-prompts/" directories in the working directory. Added fallback to "/tmp" following the pattern already used in src/mcp/github-actions-server.ts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- src/create-prompt/index.ts | 4 ++-- src/modes/agent/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 5f6d6c7..18f9c32 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -836,7 +836,7 @@ export async function createPrompt( modeContext.claudeBranch, ); - await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, { + await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, { recursive: true, }); @@ -855,7 +855,7 @@ export async function createPrompt( // Write the prompt file await writeFile( - `${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`, + `${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`, promptContent, ); diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index f7d889c..d96ba84 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -45,7 +45,7 @@ export const agentMode: Mode = { // TODO: handle by createPrompt (similar to tag and review modes) // Create prompt directory - await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, { + await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, { recursive: true, }); // Write the prompt file - the base action requires a prompt_file parameter, @@ -57,7 +57,7 @@ export const agentMode: Mode = { context.inputs.directPrompt || `Repository: ${context.repository.owner}/${context.repository.repo}`; await writeFile( - `${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`, + `${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`, promptContent, ); From db364128545c317a6decf48030da71eb85abcb29 Mon Sep 17 00:00:00 2001 From: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:13:34 +0100 Subject: [PATCH 14/17] provides github token for claude code action (#462) Currently when running the `gh` command in the action, there is an error in the action logs that suggests that the GH_TOKEN isn't being set. We've solved this internally in our company by providing the GH_TOKEN in the action. --- .github/workflows/issue-triage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 322b12d..beaeef2 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -104,3 +104,5 @@ jobs: mcp_config: /tmp/mcp-config/mcp-servers.json timeout_minutes: "5" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8f0a7fe9d35c92432ae635cdfd1d10a13862bcc6 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 18 Aug 2025 15:50:27 -0700 Subject: [PATCH 15/17] clarify workflow validation message (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the workflow validation message to be more specific about when Claude Code workflows will start working, providing clearer guidance to users experiencing this validation error. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- src/github/token.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/github/token.ts b/src/github/token.ts index da30285..6cb9079 100644 --- a/src/github/token.ts +++ b/src/github/token.ts @@ -42,17 +42,14 @@ async function exchangeForAppToken(oidcToken: string): Promise { // Check for specific workflow validation error codes that should skip the action const errorCode = responseJson.error?.details?.error_code; - if ( - errorCode === "workflow_not_found_on_default_branch" || - errorCode === "workflow_content_mismatch" - ) { + if (errorCode === "workflow_not_found_on_default_branch") { const message = responseJson.message ?? responseJson.error?.message ?? "Workflow validation failed"; core.warning(`Skipping action due to workflow validation: ${message}`); console.log( - "Action skipped due to workflow validation error. This is expected when adding Claude Code workflows to new repositories or on PRs with workflow changes.", + "Action skipped due to workflow validation error. This is expected when adding Claude Code workflows to new repositories or on PRs with workflow changes. If you're seeing this, your workflow will begin working once you merge your PR.", ); core.setOutput("skipped_due_to_workflow_validation_mismatch", "true"); process.exit(0); From 900322ca88ad44dddd909e382f07e55c999fb19d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 18 Aug 2025 23:43:42 +0000 Subject: [PATCH 16/17] chore: bump Claude Code version to 1.0.84 --- action.yml | 2 +- base-action/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index d1f8434..ed5bc77 100644 --- a/action.yml +++ b/action.yml @@ -177,7 +177,7 @@ runs: echo "Base-action dependencies installed" cd - # Install Claude Code globally - curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 + curl -fsSL https://claude.ai/install.sh | bash -s 1.0.84 echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Setup Network Restrictions diff --git a/base-action/action.yml b/base-action/action.yml index 20c632d..8bf7e9e 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -118,7 +118,7 @@ runs: - name: Install Claude Code shell: bash - run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 + run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.84 - name: Run Claude Code Action shell: bash From 68b7ca379c023e06c7d12ad2c81e346b1670cf8c Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 18 Aug 2025 17:00:18 -0700 Subject: [PATCH 17/17] include input bools in claude env (#464) --- action.yml | 2 ++ base-action/src/run-claude.ts | 12 +++++-- src/entrypoints/collect-inputs.ts | 59 +++++++++++++++++++++++++++++++ src/entrypoints/prepare.ts | 3 ++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/entrypoints/collect-inputs.ts diff --git a/action.yml b/action.yml index ed5bc77..d4631fb 100644 --- a/action.yml +++ b/action.yml @@ -166,6 +166,7 @@ runs: DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} + ALL_INPUTS: ${{ toJson(inputs) }} - name: Install Base Action Dependencies if: steps.prepare.outputs.contains_trigger == 'true' @@ -212,6 +213,7 @@ runs: INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands + INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }} # Model configuration ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }} diff --git a/base-action/src/run-claude.ts b/base-action/src/run-claude.ts index 70e38d7..0edfa72 100644 --- a/base-action/src/run-claude.ts +++ b/base-action/src/run-claude.ts @@ -110,6 +110,10 @@ export function prepareRunConfig( // Parse custom environment variables const customEnv = parseCustomEnvVars(options.claudeEnv); + if (process.env.INPUT_ACTION_INPUTS_PRESENT) { + customEnv.GITHUB_ACTION_INPUTS = process.env.INPUT_ACTION_INPUTS_PRESENT; + } + return { claudeArgs, promptPath, @@ -142,9 +146,11 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) { console.log(`Prompt file size: ${promptSize} bytes`); // Log custom environment variables if any - if (Object.keys(config.env).length > 0) { - const envKeys = Object.keys(config.env).join(", "); - console.log(`Custom environment variables: ${envKeys}`); + const customEnvKeys = Object.keys(config.env).filter( + (key) => key !== "CLAUDE_ACTION_INPUTS_PRESENT", + ); + if (customEnvKeys.length > 0) { + console.log(`Custom environment variables: ${customEnvKeys.join(", ")}`); } // Output to console diff --git a/src/entrypoints/collect-inputs.ts b/src/entrypoints/collect-inputs.ts new file mode 100644 index 0000000..501a438 --- /dev/null +++ b/src/entrypoints/collect-inputs.ts @@ -0,0 +1,59 @@ +import * as core from "@actions/core"; + +export function collectActionInputsPresence(): void { + const inputDefaults: Record = { + trigger_phrase: "@claude", + assignee_trigger: "", + label_trigger: "claude", + base_branch: "", + branch_prefix: "claude/", + allowed_bots: "", + mode: "tag", + model: "", + anthropic_model: "", + fallback_model: "", + allowed_tools: "", + disallowed_tools: "", + custom_instructions: "", + direct_prompt: "", + override_prompt: "", + mcp_config: "", + additional_permissions: "", + claude_env: "", + settings: "", + anthropic_api_key: "", + claude_code_oauth_token: "", + github_token: "", + max_turns: "", + use_sticky_comment: "false", + use_commit_signing: "false", + experimental_allowed_domains: "", + }; + + const allInputsJson = process.env.ALL_INPUTS; + if (!allInputsJson) { + console.log("ALL_INPUTS environment variable not found"); + core.setOutput("action_inputs_present", JSON.stringify({})); + return; + } + + let allInputs: Record; + try { + allInputs = JSON.parse(allInputsJson); + } catch (e) { + console.error("Failed to parse ALL_INPUTS JSON:", e); + core.setOutput("action_inputs_present", JSON.stringify({})); + return; + } + + const presentInputs: Record = {}; + + for (const [name, defaultValue] of Object.entries(inputDefaults)) { + const actualValue = allInputs[name] || ""; + + const isSet = actualValue !== defaultValue; + presentInputs[name] = isSet; + } + + core.setOutput("action_inputs_present", JSON.stringify(presentInputs)); +} diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index b9995df..b151590 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -13,9 +13,12 @@ import { parseGitHubContext, isEntityContext } from "../github/context"; import { getMode, isValidMode, DEFAULT_MODE } from "../modes/registry"; import type { ModeName } from "../modes/types"; import { prepare } from "../prepare"; +import { collectActionInputsPresence } from "./collect-inputs"; async function run() { try { + collectActionInputsPresence(); + // Step 1: Get mode first to determine authentication method const modeInput = process.env.MODE || DEFAULT_MODE;