Compare commits

...
Author SHA1 Message Date
Ashwin Bhat ece24efbb4 debug: add logging to credential clearing 2026-02-15 15:00:51 -08:00
Ashwin Bhat fea6d7077e fix: clear actions/checkout includeIf credential configs
actions/checkout now stores git credentials in external config files
via includeIf instead of directly in .git/config. Our configureGitAuth
was only unsetting extraheader from local config, missing the included
files. This caused git push to use the stale github-actions[bot] token
instead of the Claude app token.

Now we enumerate all config origins and clear extraheader entries from
any included credential files.
2026-02-15 14:54:33 -08:00
Ashwin BhatandGitHub 68cfeead18 Revert "fix: replace deprecated :* with modern * wildcard in git permissions (#929)" (#949)
This reverts commit 1bb0e7464b.
2026-02-15 14:39:25 -08:00
Ashwin BhatandGitHub f5088835af Fix stale claudeCodeVersion in run.ts and update bump automation (#943) 2026-02-13 15:12:56 -08:00
GitHub Actions ea36d6abde chore: bump Claude Code to 2.1.42 and Agent SDK to 0.2.42 2026-02-13 19:55:13 +00:00
c22f7c3f9d revert: undo PR checkout fork support and unique branch naming (#937)
Reverts the following commits:
- f669191 fix: use unique local branch names for PR checkout to avoid conflicts (#931)
- 21e3fe0 Fix PR checkout to support fork PRs (#851)

Simplifies PR branch checkout back to using headRefName directly instead
of the pr-{number} local branch naming scheme introduced in #931 and the
GitHub pull ref fetch approach introduced in #851.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 09:45:01 -08:00
f669191d7d fix: use unique local branch names for PR checkout to avoid conflicts (#931)
The previous implementation used the PR's original branch name when
fetching, which could conflict with existing local or remote branches
of the same name. This caused checkout failures for PRs with common
branch names like 'main' or 'feature/xyz'.

Changes:
- Use 'pr-{number}' format for local branch names (e.g., pr-385)
- Preserve original branch name for logging purposes
- Add detailed logging showing original -> local branch mapping

This ensures uniqueness since PR numbers are unique per repository,
while maintaining support for both same-repo and fork PRs via
GitHub's pull/{number}/head refs.

Fixes the issue introduced in #851 where fork PR support was added.

Co-authored-by: Yi-Cheng Wang <yicheng.wang@heph-ai.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 19:30:25 -08:00
Octavian GuzuandGitHub 8c383c5de3 fix: skip CI MCP server installation when actions:read permission is missing (#933) 2026-02-11 11:02:49 -08:00
1bb0e7464b fix: replace deprecated :* with modern * wildcard in git permissions (#929)
Replace `Bash(git add:*)` syntax with `Bash(git add *)` in default
tool permissions for tag mode and create-prompt. The colon-prefixed
wildcard syntax is deprecated and causes SDK validation errors.

Closes #856

Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:26:10 -08:00
10 changed files with 106 additions and 35 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ runs:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: |
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.1.39"
CLAUDE_CODE_VERSION="2.1.42"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do
echo "Installation attempt $attempt..."
+2 -2
View File
@@ -6,7 +6,7 @@
"name": "@anthropic-ai/claude-code-base-action",
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.39",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"shell-quote": "^1.8.3",
},
"devDependencies": {
@@ -27,7 +27,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.39", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-wR1TBH62X6E1YwRnWa+A2Eau7AfpTWtfpnwQXO3yRY31FtmzOjPkQb93hbF3AkT0WL7YF9mxBBwJKUa3ZEc5+A=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.42", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-/CugP7AjP57Dqtl2sbsDtxdbpQoPKIhjyF5WrTViGu4NHQdM+UikrRs4MhZ2jeotiC5R7iK9ZUN9SiBgcZ8oLw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
+1 -1
View File
@@ -11,7 +11,7 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.39",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"shell-quote": "^1.8.3"
},
"devDependencies": {
+2 -2
View File
@@ -7,7 +7,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.39",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
@@ -37,7 +37,7 @@
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.39", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-wR1TBH62X6E1YwRnWa+A2Eau7AfpTWtfpnwQXO3yRY31FtmzOjPkQb93hbF3AkT0WL7YF9mxBBwJKUa3ZEc5+A=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.42", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-/CugP7AjP57Dqtl2sbsDtxdbpQoPKIhjyF5WrTViGu4NHQdM+UikrRs4MhZ2jeotiC5R7iK9ZUN9SiBgcZ8oLw=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
+1 -1
View File
@@ -12,7 +12,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/claude-agent-sdk": "^0.2.39",
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
"@modelcontextprotocol/sdk": "^1.11.0",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
+1 -1
View File
@@ -51,7 +51,7 @@ async function installClaudeCode(): Promise<void> {
return;
}
const claudeCodeVersion = "2.1.31";
const claudeCodeVersion = "2.1.42";
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
for (let attempt = 1; attempt <= 3; attempt++) {
+1 -7
View File
@@ -166,13 +166,7 @@ export async function setupBranch(
// Execute git commands to checkout PR branch (dynamic depth based on PR size)
// Using execFileSync instead of shell template literals for security
// Use GitHub's PR ref which works for both same-repo and fork PRs
execGit([
"fetch",
"origin",
`--depth=${fetchDepth}`,
`pull/${entityNumber}/head:${branchName}`,
]);
execGit(["fetch", "origin", `--depth=${fetchDepth}`, branchName]);
execGit(["checkout", branchName, "--"]);
console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
+46 -4
View File
@@ -42,13 +42,55 @@ export async function configureGitAuth(
await $`git config user.email "${botId}+${botName}@${noreplyDomain}"`;
console.log(`✓ Set git user as ${botName}`);
// Remove the authorization header that actions/checkout sets
// Remove the authorization header that actions/checkout sets.
// Newer versions of actions/checkout store credentials in external config files
// referenced via includeIf, so --unset-all on local config may not find them.
// We clear both: local config entries and any includeIf credential files.
console.log("Removing existing git authentication headers...");
try {
await $`git config --unset-all http.${GITHUB_SERVER_URL}/.extraheader`;
console.log("✓ Removed existing authentication headers");
} catch (e) {
console.log("No existing authentication headers to remove");
console.log("✓ Removed existing authentication headers from local config");
} catch {
console.log("No existing authentication headers in local config to remove");
}
// Clear extraheader from includeIf credential config files set by actions/checkout
try {
const configOutput = await $`git config --list --show-origin`.text();
const extraheaderPattern = `http.${GITHUB_SERVER_URL}/.extraheader=`;
console.log(
`Scanning git config origins for extraheader pattern: ${extraheaderPattern}`,
);
let foundExternalEntries = false;
for (const line of configOutput.split("\n")) {
if (!line.includes(extraheaderPattern)) continue;
// Format: "file:<path>\t<key>=<value>"
const match = line.match(/^file:(.+?)\t/);
if (!match) {
console.log(
`Found extraheader but could not parse origin: ${line.substring(0, 80)}`,
);
continue;
}
const configFile = match[1]!;
// Skip the local .git/config — we already handled that above
if (configFile.endsWith("/.git/config")) {
console.log("Skipping .git/config (already handled)");
continue;
}
foundExternalEntries = true;
try {
await $`git config --file ${configFile} --unset-all http.${GITHUB_SERVER_URL}/.extraheader`;
console.log(`✓ Removed authentication headers from ${configFile}`);
} catch {
console.log(`Could not unset extraheader from ${configFile}`);
}
}
if (!foundExternalEntries) {
console.log("No external credential config files found");
}
} catch (error) {
console.log(`Could not enumerate git config origins: ${error}`);
}
// Update the remote URL to include the token for authentication
+18 -16
View File
@@ -177,25 +177,27 @@ export async function prepareMcpConfig(
if (!actuallyHasPermission) {
core.warning(
"The github_ci MCP server requires 'actions: read' permission. " +
"Please ensure your GitHub token has this permission. " +
"Skipping CI server installation. " +
"To enable CI status checks, add 'actions: read' to your workflow permissions. " +
"See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token",
);
} else {
baseMcpConfig.mcpServers.github_ci = {
command: "bun",
args: [
"run",
`${process.env.GITHUB_ACTION_PATH}/src/mcp/github-actions-server.ts`,
],
env: {
// Use workflow github token, not app token
GITHUB_TOKEN: process.env.DEFAULT_WORKFLOW_TOKEN,
REPO_OWNER: owner,
REPO_NAME: repo,
PR_NUMBER: context.entityNumber?.toString() || "",
RUNNER_TEMP: process.env.RUNNER_TEMP || "/tmp",
},
};
}
baseMcpConfig.mcpServers.github_ci = {
command: "bun",
args: [
"run",
`${process.env.GITHUB_ACTION_PATH}/src/mcp/github-actions-server.ts`,
],
env: {
// Use workflow github token, not app token
GITHUB_TOKEN: process.env.DEFAULT_WORKFLOW_TOKEN,
REPO_OWNER: owner,
REPO_NAME: repo,
PR_NUMBER: context.entityNumber?.toString() || "",
RUNNER_TEMP: process.env.RUNNER_TEMP || "/tmp",
},
};
}
if (hasGitHubMcpTools) {
+33
View File
@@ -9,6 +9,7 @@ describe("prepareMcpConfig", () => {
let consoleWarningSpy: any;
let setFailedSpy: any;
let processExitSpy: any;
let fetchSpy: any;
// Create a mock context for tests
const mockContext: ParsedGitHubContext = {
@@ -66,6 +67,10 @@ describe("prepareMcpConfig", () => {
processExitSpy = spyOn(process, "exit").mockImplementation(() => {
throw new Error("Process exit");
});
// Mock fetch so checkActionsReadPermission succeeds (returns 200 for actions API)
fetchSpy = spyOn(global, "fetch").mockResolvedValue(
new Response(JSON.stringify({ workflow_runs: [] }), { status: 200 }),
);
// Set up required environment variables
if (!process.env.GITHUB_ACTION_PATH) {
@@ -78,6 +83,7 @@ describe("prepareMcpConfig", () => {
consoleWarningSpy.mockRestore();
setFailedSpy.mockRestore();
processExitSpy.mockRestore();
fetchSpy.mockRestore();
});
test("should return comment server when commit signing is disabled", async () => {
@@ -263,6 +269,33 @@ describe("prepareMcpConfig", () => {
expect(parsed.mcpServers.github_ci).not.toBeDefined();
});
test("should not include github_ci server when actions:read permission is missing", async () => {
process.env.DEFAULT_WORKFLOW_TOKEN = "workflow-token";
// Simulate 403 from actions API
fetchSpy.mockResolvedValue(
new Response(
JSON.stringify({ message: "Resource not accessible by integration" }),
{ status: 403 },
),
);
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [],
mode: "tag",
context: mockPRContext,
});
const parsed = JSON.parse(result);
expect(parsed.mcpServers.github_ci).not.toBeDefined();
delete process.env.DEFAULT_WORKFLOW_TOKEN;
});
test("should not include github_ci server when DEFAULT_WORKFLOW_TOKEN is missing", async () => {
delete process.env.DEFAULT_WORKFLOW_TOKEN;