diff --git a/src/entrypoints/run.ts b/src/entrypoints/run.ts index 4a57395..1848603 100644 --- a/src/entrypoints/run.ts +++ b/src/entrypoints/run.ts @@ -229,8 +229,8 @@ async function run() { // Restore them from the base branch before the CLI reads them. // // We read pull_request.base.ref from the payload directly because agent - // mode's branchInfo.baseBranch defaults to "main" rather than the PR's - // actual target (agent/index.ts). For issue_comment on a PR the payload + // mode's branchInfo.baseBranch defaults to the repo's default branch rather + // than the PR's actual target (agent/index.ts). For issue_comment on a PR the payload // lacks base.ref, so we fall back to the mode-provided value — tag mode // fetches it from GraphQL; agent mode on issue_comment is an edge case // that at worst restores from the wrong trusted branch (still secure). @@ -312,7 +312,7 @@ async function run() { commentId, githubToken, claudeBranch, - baseBranch: baseBranch || "main", + baseBranch: baseBranch || context.repository.default_branch || "main", triggerUsername: context.actor, context, octokit, diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index c7bd8d6..c0963e8 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -253,7 +253,8 @@ async function run() { commentId: parseInt(process.env.CLAUDE_COMMENT_ID!), githubToken, claudeBranch: process.env.CLAUDE_BRANCH, - baseBranch: process.env.BASE_BRANCH || "main", + baseBranch: + process.env.BASE_BRANCH || context.repository.default_branch || "main", triggerUsername: process.env.TRIGGER_USERNAME, context, octokit, diff --git a/src/github/context.ts b/src/github/context.ts index a0b388c..eeefb99 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -79,6 +79,7 @@ type BaseContext = { owner: string; repo: string; full_name: string; + default_branch?: string; }; actor: string; inputs: { @@ -140,6 +141,7 @@ export function parseGitHubContext(): GitHubContext { owner: context.repo.owner, repo: context.repo.repo, full_name: `${context.repo.owner}/${context.repo.repo}`, + default_branch: context.payload.repository?.default_branch, }, actor: context.actor, inputs: { diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index e604737..b648716 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -85,15 +85,15 @@ export async function prepareAgentMode({ // Check for branch info from environment variables (useful for auto-fix workflows) const claudeBranch = process.env.CLAUDE_BRANCH || undefined; - const baseBranch = - process.env.BASE_BRANCH || context.inputs.baseBranch || "main"; + const defaultBranch = context.repository.default_branch || "main"; + const baseBranch = context.inputs.baseBranch || defaultBranch; // Detect current branch from GitHub environment const currentBranch = claudeBranch || process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || - "main"; + defaultBranch; // Get our GitHub MCP servers config const ourMcpConfig = await prepareMcpConfig({ diff --git a/test/mockContext.ts b/test/mockContext.ts index c02caa4..324104e 100644 --- a/test/mockContext.ts +++ b/test/mockContext.ts @@ -36,6 +36,7 @@ const defaultRepository = { owner: "test-owner", repo: "test-repo", full_name: "test-owner/test-repo", + default_branch: "main", }; type MockContextOverrides = Omit, "inputs"> & { diff --git a/test/modes/agent.test.ts b/test/modes/agent.test.ts index be6f0d2..1404b0d 100644 --- a/test/modes/agent.test.ts +++ b/test/modes/agent.test.ts @@ -88,7 +88,7 @@ describe("Agent Mode", () => { expect(result.claudeArgs).toBe("--model claude-sonnet-4 --max-turns 10"); expect(result.claudeArgs).not.toContain("--mcp-config"); - // Verify return structure - should use "main" as fallback when no env vars set + // Verify return structure - should fall back to repository.default_branch when no env vars set expect(result).toEqual({ commentId: undefined, branchInfo: { @@ -108,6 +108,60 @@ describe("Agent Mode", () => { process.env.GITHUB_REF_NAME = originalRefName; }); + test("prepare falls back to repository.default_branch when not 'main'", async () => { + const contextWithDevelop = createMockAutomationContext({ + eventName: "workflow_dispatch", + repository: { + owner: "test-owner", + repo: "test-repo", + full_name: "test-owner/test-repo", + default_branch: "develop", + }, + }); + + // Save and clear env vars that would otherwise override the fallback + const originalClaudeBranch = process.env.CLAUDE_BRANCH; + const originalHeadRef = process.env.GITHUB_HEAD_REF; + const originalRefName = process.env.GITHUB_REF_NAME; + delete process.env.CLAUDE_BRANCH; + delete process.env.GITHUB_HEAD_REF; + delete process.env.GITHUB_REF_NAME; + + const mockOctokit = { + rest: { + users: { + getAuthenticated: mock(() => + Promise.resolve({ + data: { login: "test-user", id: 12345, type: "User" }, + }), + ), + getByUsername: mock(() => + Promise.resolve({ + data: { login: "test-user", id: 12345, type: "User" }, + }), + ), + }, + }, + } as any; + + const result = await prepareAgentMode({ + context: contextWithDevelop, + octokit: mockOctokit, + githubToken: "test-token", + }); + + expect(result.branchInfo.baseBranch).toBe("develop"); + expect(result.branchInfo.currentBranch).toBe("develop"); + + // Restore env vars + if (originalClaudeBranch !== undefined) + process.env.CLAUDE_BRANCH = originalClaudeBranch; + if (originalHeadRef !== undefined) + process.env.GITHUB_HEAD_REF = originalHeadRef; + if (originalRefName !== undefined) + process.env.GITHUB_REF_NAME = originalRefName; + }); + test("prepare rejects bot actors without allowed_bots", async () => { const contextWithPrompts = createMockAutomationContext({ eventName: "workflow_dispatch",