mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 14:24:13 +08:00
fix: Encode branch names in URLs to prevent truncation in markdown links
Branch names containing special characters (particularly parentheses) were breaking markdown links in GitHub comments. This caused URLs to be truncated when clicked. Changes: - Add encodeBranchName() helper function that: - Uses encodeURIComponent for basic encoding - Preserves forward slashes (GitHub expects literal / in branch URLs) - Manually encodes parentheses (not encoded by encodeURIComponent per RFC 3986) - Apply encoding to branch URLs in: - update-comment-link.ts (PR compare URLs) - branch-cleanup.ts (branch tree URLs) - comment-logic.ts (branch tree URLs) - comments/common.ts (branch tree URLs) - Improve PR link regex to use greedy match with end anchor - Add test for branch names with special characters
This commit is contained in:
@@ -15,6 +15,20 @@ import { GITHUB_SERVER_URL } from "../github/api/config";
|
|||||||
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
||||||
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a branch name for use in a URL, preserving forward slashes.
|
||||||
|
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
|
||||||
|
* but other special characters like parentheses need to be encoded.
|
||||||
|
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
|
||||||
|
* but parentheses break markdown links so we encode them manually.
|
||||||
|
*/
|
||||||
|
function encodeBranchName(branchName: string): string {
|
||||||
|
return encodeURIComponent(branchName)
|
||||||
|
.replace(/%2F/gi, "/")
|
||||||
|
.replace(/\(/g, "%28")
|
||||||
|
.replace(/\)/g, "%29");
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
|
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
|
||||||
@@ -140,7 +154,7 @@ async function run() {
|
|||||||
const prBody = encodeURIComponent(
|
const prBody = encodeURIComponent(
|
||||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
||||||
);
|
);
|
||||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${encodeBranchName(baseBranch)}...${encodeBranchName(claudeBranch)}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
||||||
prLink = `\n[Create a PR](${prUrl})`;
|
prLink = `\n[Create a PR](${prUrl})`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -2,6 +2,20 @@ import type { Octokits } from "../api/client";
|
|||||||
import { GITHUB_SERVER_URL } from "../api/config";
|
import { GITHUB_SERVER_URL } from "../api/config";
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a branch name for use in a URL, preserving forward slashes.
|
||||||
|
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
|
||||||
|
* but other special characters like parentheses need to be encoded.
|
||||||
|
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
|
||||||
|
* but parentheses break markdown links so we encode them manually.
|
||||||
|
*/
|
||||||
|
function encodeBranchName(branchName: string): string {
|
||||||
|
return encodeURIComponent(branchName)
|
||||||
|
.replace(/%2F/gi, "/")
|
||||||
|
.replace(/\(/g, "%28")
|
||||||
|
.replace(/\)/g, "%29");
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkAndCommitOrDeleteBranch(
|
export async function checkAndCommitOrDeleteBranch(
|
||||||
octokit: Octokits,
|
octokit: Octokits,
|
||||||
owner: string,
|
owner: string,
|
||||||
@@ -80,7 +94,7 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Set branch link since we now have commits
|
// Set branch link since we now have commits
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -91,7 +105,7 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
} catch (gitError) {
|
} catch (gitError) {
|
||||||
console.error("Error checking/committing changes:", gitError);
|
console.error("Error checking/committing changes:", gitError);
|
||||||
// If we can't check git status, assume the branch might have changes
|
// If we can't check git status, assume the branch might have changes
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -102,13 +116,13 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only add branch link if there are commits
|
// Only add branch link if there are commits
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error comparing commits on Claude branch:", error);
|
console.error("Error comparing commits on Claude branch:", error);
|
||||||
// If we can't compare but the branch exists remotely, include the branch link
|
// If we can't compare but the branch exists remotely, include the branch link
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
import { GITHUB_SERVER_URL } from "../api/config";
|
import { GITHUB_SERVER_URL } from "../api/config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a branch name for use in a URL, preserving forward slashes.
|
||||||
|
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
|
||||||
|
* but other special characters like parentheses need to be encoded.
|
||||||
|
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
|
||||||
|
* but parentheses break markdown links so we encode them manually.
|
||||||
|
*/
|
||||||
|
function encodeBranchName(branchName: string): string {
|
||||||
|
return encodeURIComponent(branchName)
|
||||||
|
.replace(/%2F/gi, "/")
|
||||||
|
.replace(/\(/g, "%28")
|
||||||
|
.replace(/\)/g, "%29");
|
||||||
|
}
|
||||||
|
|
||||||
export type ExecutionDetails = {
|
export type ExecutionDetails = {
|
||||||
total_cost_usd?: number;
|
total_cost_usd?: number;
|
||||||
duration_ms?: number;
|
duration_ms?: number;
|
||||||
@@ -160,7 +174,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
// Extract owner/repo from jobUrl
|
// Extract owner/repo from jobUrl
|
||||||
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
|
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
|
||||||
if (repoMatch) {
|
if (repoMatch) {
|
||||||
branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${finalBranchName}`;
|
branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${encodeBranchName(finalBranchName)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +186,9 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add PR link (either from content or provided)
|
// Add PR link (either from content or provided)
|
||||||
|
// Use greedy match with end anchor to capture full URL even if it contains parentheses
|
||||||
const prUrl =
|
const prUrl =
|
||||||
prLinkFromContent || (prLink ? prLink.match(/\(([^)]+)\)/)?.[1] : "");
|
prLinkFromContent || (prLink ? prLink.match(/\((.+)\)$/)?.[1] : "");
|
||||||
if (prUrl) {
|
if (prUrl) {
|
||||||
links += ` • [Create PR ➔](${prUrl})`;
|
links += ` • [Create PR ➔](${prUrl})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,26 @@ export function createJobRunLink(
|
|||||||
return `[View job run](${jobRunUrl})`;
|
return `[View job run](${jobRunUrl})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a branch name for use in a URL, preserving forward slashes.
|
||||||
|
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
|
||||||
|
* but other special characters like parentheses need to be encoded.
|
||||||
|
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
|
||||||
|
* but parentheses break markdown links so we encode them manually.
|
||||||
|
*/
|
||||||
|
function encodeBranchName(branchName: string): string {
|
||||||
|
return encodeURIComponent(branchName)
|
||||||
|
.replace(/%2F/gi, "/")
|
||||||
|
.replace(/\(/g, "%28")
|
||||||
|
.replace(/\)/g, "%29");
|
||||||
|
}
|
||||||
|
|
||||||
export function createBranchLink(
|
export function createBranchLink(
|
||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
branchName: string,
|
branchName: string,
|
||||||
): string {
|
): string {
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${branchName}`;
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(branchName)}`;
|
||||||
return `\n[View branch](${branchUrl})`;
|
return `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,21 @@ describe("updateCommentBody", () => {
|
|||||||
);
|
);
|
||||||
expect(result).not.toContain("View branch");
|
expect(result).not.toContain("View branch");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("encodes special characters in branch names while preserving slashes", () => {
|
||||||
|
const input = {
|
||||||
|
...baseInput,
|
||||||
|
branchName: "feature/fix(issue)-test",
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = updateCommentBody(input);
|
||||||
|
// Branch name display should show the original name
|
||||||
|
expect(result).toContain("`feature/fix(issue)-test`");
|
||||||
|
// URL should have encoded parentheses but preserved slashes
|
||||||
|
expect(result).toContain(
|
||||||
|
"https://github.com/owner/repo/tree/feature/fix%28issue%29-test",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("PR link", () => {
|
describe("PR link", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user