Compare commits

...

15 Commits

Author SHA1 Message Date
Ashwin Bhat
0b138d9d49 Update token.ts copy (#450) 2025-08-14 16:42:49 -07:00
GitHub Actions
c34e066a3b chore: bump Claude Code version to 1.0.81 2025-08-14 17:00:23 +00:00
GitHub Actions
449c6791bd chore: bump Claude Code version to 1.0.80 2025-08-13 21:17:49 +00:00
GitHub Actions
2b67ac084b chore: bump Claude Code version to 1.0.77 2025-08-13 20:33:11 +00:00
GitHub Actions
76de8a48fc chore: bump Claude Code version to 1.0.79 2025-08-13 20:26:17 +00:00
GitHub Actions
a80505bbfb chore: bump Claude Code version to 1.0.77 2025-08-12 19:25:39 +00:00
GitHub Actions
af23644a50 chore: bump Claude Code version to 1.0.76 2025-08-12 18:10:59 +00:00
GitHub Actions
98e6a902bf chore: bump Claude Code version to 1.0.74 2025-08-12 16:19:34 +00:00
GitHub Actions
8b2bd6d04f chore: bump Claude Code version to 1.0.73 2025-08-11 23:43:47 +00:00
Ashwin Bhat
4f4f43f044 docs: add prominent notice about upcoming v1.0 breaking changes (#437)
- Add GitHub alert box highlighting the v1.0 roadmap
- Link to discussion #428 for community feedback
- Briefly summarize key changes (automatic mode selection, unified prompt interface)
- Position prominently at top of README for maximum visibility
2025-08-10 16:19:08 -07:00
Matthew Burke
8a5d751740 fix - allowed and disallowed tools ignored in agent mode (#424) 2025-08-08 14:34:55 -07:00
GitHub Actions
bc423b47f5 chore: bump Claude Code version to 1.0.72 2025-08-08 18:16:40 +00:00
Steve
6d5c92076b non negative line validation for comment server (#429)
* enforce non-negative validation for line in GH comment server

* include  .nonnegative() for startLine too
2025-08-08 08:36:20 -07:00
Yuku Kotani
fec554fc7c feat: add flexible bot access control with allowed_bots option (#117)
* feat: skip permission check for GitHub App bot users

GitHub Apps (users ending with [bot]) now bypass permission checks
as they have their own authorization mechanism.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add allow_bot_users option to control bot user access

- Add allow_bot_users input parameter (default: false)
- Modify checkHumanActor to optionally allow bot users
- Add comprehensive tests for bot user handling
- Improve security by blocking bot users by default

This change prevents potential prompt injection attacks from bot users
while providing flexibility for trusted bot integrations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: mark bot user support feature as completed in roadmap

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: move allowedBots parameter to context object

Move allowedBots from function parameter to context.inputs to maintain
consistency with other input handling throughout the codebase.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: update README for bot user support feature

Add documentation for the new allowed_bots parameter that enables
bot users to trigger Claude actions with granular control.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add missing allowedBots property in permissions test

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: update bot name format to include [bot] suffix in tests and docs

- Update test cases to use correct bot actor names with [bot] suffix
- Update documentation example to show correct bot name format
- Align with GitHub's actual bot naming convention

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: normalize bot names for allowed_bots validation

- Strip [bot] suffix from both actor names and allowed bot list for comparison
- Allow both "dependabot" and "dependabot[bot]" formats in allowed_bots input
- Display normalized bot names in error messages for consistency
- Add comprehensive test coverage for both naming formats

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-07 18:03:20 -07:00
GitHub Actions
59ca6e42d9 chore: bump Claude Code version to 1.0.71 2025-08-07 22:57:57 +00:00
20 changed files with 268 additions and 22 deletions

View File

@@ -14,6 +14,19 @@ A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs an
- 📋 **Progress Tracking**: Visual progress indicators with checkboxes that dynamically update as Claude completes tasks - 📋 **Progress Tracking**: Visual progress indicators with checkboxes that dynamically update as Claude completes tasks
- 🏃 **Runs on Your Infrastructure**: The action executes entirely on your own GitHub runner (Anthropic API calls go to your chosen provider) - 🏃 **Runs on Your Infrastructure**: The action executes entirely on your own GitHub runner (Anthropic API calls go to your chosen provider)
## ⚠️ **BREAKING CHANGES COMING IN v1.0** ⚠️
**We're planning a major update that will significantly change how this action works.** The new version will:
- ✨ Automatically select the appropriate mode (no more `mode` input)
- 🔧 Simplify configuration with unified `prompt` and `claude_args`
- 🚀 Align more closely with the Claude Code SDK capabilities
- 💥 Remove multiple inputs like `direct_prompt`, `custom_instructions`, and others
**[→ Read the full v1.0 roadmap and provide feedback](https://github.com/anthropics/claude-code-action/discussions/428)**
---
## Quickstart ## Quickstart
The easiest way to set up this action is through [Claude Code](https://claude.ai/code) in the terminal. Just open `claude` and run `/install-github-app`. The easiest way to set up this action is through [Claude Code](https://claude.ai/code) in the terminal. Just open `claude` and run `/install-github-app`.

View File

@@ -10,7 +10,7 @@ Thank you for trying out the beta of our GitHub Action! This document outlines o
- **Support for workflow_dispatch and repository_dispatch events** - Dispatch Claude on events triggered via API from other workflows or from other services - **Support for workflow_dispatch and repository_dispatch events** - Dispatch Claude on events triggered via API from other workflows or from other services
- **Ability to disable commit signing** - Option to turn off GPG signing for environments where it's not required. This will enable Claude to use normal `git` bash commands for committing. This will likely become the default behavior once added. - **Ability to disable commit signing** - Option to turn off GPG signing for environments where it's not required. This will enable Claude to use normal `git` bash commands for committing. This will likely become the default behavior once added.
- **Better code review behavior** - Support inline comments on specific lines, provide higher quality reviews with more actionable feedback - **Better code review behavior** - Support inline comments on specific lines, provide higher quality reviews with more actionable feedback
- **Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude - ~**Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude~
- **Customizable base prompts** - Full control over Claude's initial context with template variables like `$PR_COMMENTS`, `$PR_FILES`, etc. Users can replace our default prompt entirely while still accessing key contextual data - **Customizable base prompts** - Full control over Claude's initial context with template variables like `$PR_COMMENTS`, `$PR_FILES`, etc. Users can replace our default prompt entirely while still accessing key contextual data
--- ---

View File

@@ -23,6 +23,10 @@ inputs:
description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)" description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)"
required: false required: false
default: "claude/" default: "claude/"
allowed_bots:
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no bots."
required: false
default: ""
# Mode configuration # Mode configuration
mode: mode:
@@ -156,6 +160,7 @@ runs:
OVERRIDE_PROMPT: ${{ inputs.override_prompt }} OVERRIDE_PROMPT: ${{ inputs.override_prompt }}
MCP_CONFIG: ${{ inputs.mcp_config }} MCP_CONFIG: ${{ inputs.mcp_config }}
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }} OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ID: ${{ github.run_id }}
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
@@ -172,7 +177,7 @@ runs:
echo "Base-action dependencies installed" echo "Base-action dependencies installed"
cd - cd -
# Install Claude Code globally # Install Claude Code globally
bun install -g @anthropic-ai/claude-code@1.0.70 bun install -g @anthropic-ai/claude-code@1.0.81
- name: Setup Network Restrictions - name: Setup Network Restrictions
if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != ''

View File

@@ -118,7 +118,7 @@ runs:
- name: Install Claude Code - name: Install Claude Code
shell: bash shell: bash
run: bun install -g @anthropic-ai/claude-code@1.0.70 run: bun install -g @anthropic-ai/claude-code@1.0.81
- name: Run Claude Code Action - name: Run Claude Code Action
shell: bash shell: bash

View File

@@ -207,15 +207,8 @@ Claude does **not** have access to execute arbitrary Bash commands by default. I
```yaml ```yaml
- uses: anthropics/claude-code-action@beta - uses: anthropics/claude-code-action@beta
with: with:
allowed_tools: | allowed_tools: "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell"
Bash(npm install) disallowed_tools: "TaskOutput,KillTask"
Bash(npm run test)
Edit
Replace
NotebookEditCell
disallowed_tools: |
TaskOutput
KillTask
# ... other inputs # ... other inputs
``` ```

View File

@@ -3,7 +3,7 @@
## Access Control ## Access Control
- **Repository Access**: The action can only be triggered by users with write access to the repository - **Repository Access**: The action can only be triggered by users with write access to the repository
- **No Bot Triggers**: GitHub Apps and bots cannot trigger this action - **Bot User Control**: By default, GitHub Apps and bots cannot trigger this action for security reasons. Use the `allowed_bots` parameter to enable specific bots or all bots
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in - **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered - **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions - **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions

View File

@@ -42,6 +42,8 @@ jobs:
# Optional: grant additional permissions (requires corresponding GitHub token permissions) # Optional: grant additional permissions (requires corresponding GitHub token permissions)
# additional_permissions: | # additional_permissions: |
# actions: read # actions: read
# Optional: allow bot users to trigger the action
# allowed_bots: "dependabot[bot],renovate[bot]"
``` ```
## Inputs ## Inputs
@@ -76,6 +78,7 @@ jobs:
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" | | `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" | | `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` | | `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` |
| `allowed_bots` | Comma-separated list of allowed bot usernames, or '\*' to allow all bots. Empty string (default) allows no bots | No | "" |
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex) \*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)

View File

@@ -77,6 +77,7 @@ type BaseContext = {
useStickyComment: boolean; useStickyComment: boolean;
additionalPermissions: Map<string, string>; additionalPermissions: Map<string, string>;
useCommitSigning: boolean; useCommitSigning: boolean;
allowedBots: string;
}; };
}; };
@@ -136,6 +137,7 @@ export function parseGitHubContext(): GitHubContext {
process.env.ADDITIONAL_PERMISSIONS ?? "", process.env.ADDITIONAL_PERMISSIONS ?? "",
), ),
useCommitSigning: process.env.USE_COMMIT_SIGNING === "true", useCommitSigning: process.env.USE_COMMIT_SIGNING === "true",
allowedBots: process.env.ALLOWED_BOTS ?? "",
}, },
}; };

View File

@@ -78,7 +78,7 @@ export async function setupGitHubToken(): Promise<string> {
return appToken; return appToken;
} catch (error) { } catch (error) {
core.setFailed( 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); process.exit(1);
} }

View File

@@ -21,9 +21,42 @@ export async function checkHumanActor(
console.log(`Actor type: ${actorType}`); console.log(`Actor type: ${actorType}`);
// Check bot permissions if actor is not a User
if (actorType !== "User") { if (actorType !== "User") {
const allowedBots = githubContext.inputs.allowedBots;
// Check if all bots are allowed
if (allowedBots.trim() === "*") {
console.log(
`All bots are allowed, skipping human actor check for: ${githubContext.actor}`,
);
return;
}
// Parse allowed bots list
const allowedBotsList = allowedBots
.split(",")
.map((bot) =>
bot
.trim()
.toLowerCase()
.replace(/\[bot\]$/, ""),
)
.filter((bot) => bot.length > 0);
const botName = githubContext.actor.toLowerCase().replace(/\[bot\]$/, "");
// Check if specific bot is allowed
if (allowedBotsList.includes(botName)) {
console.log(
`Bot ${botName} is in allowed list, skipping human actor check`,
);
return;
}
// Bot not allowed
throw new Error( throw new Error(
`Workflow initiated by non-human actor: ${githubContext.actor} (type: ${actorType}).`, `Workflow initiated by non-human actor: ${botName} (type: ${actorType}). Add bot to allowed_bots list or use '*' to allow all bots.`,
); );
} }

View File

@@ -17,6 +17,12 @@ export async function checkWritePermissions(
try { try {
core.info(`Checking permissions for actor: ${actor}`); core.info(`Checking permissions for actor: ${actor}`);
// Check if the actor is a GitHub App (bot user)
if (actor.endsWith("[bot]")) {
core.info(`Actor is a GitHub App: ${actor}`);
return true;
}
// Check permissions directly using the permission endpoint // Check permissions directly using the permission endpoint
const response = await octokit.repos.getCollaboratorPermissionLevel({ const response = await octokit.repos.getCollaboratorPermissionLevel({
owner: repository.owner, owner: repository.owner,

View File

@@ -41,12 +41,14 @@ server.tool(
), ),
line: z line: z
.number() .number()
.nonnegative()
.optional() .optional()
.describe( .describe(
"Line number for single-line comments (required if startLine is not provided)", "Line number for single-line comments (required if startLine is not provided)",
), ),
startLine: z startLine: z
.number() .number()
.nonnegative()
.optional() .optional()
.describe( .describe(
"Start line for multi-line comments (use with line parameter for the end line)", "Start line for multi-line comments (use with line parameter for the end line)",

View File

@@ -80,9 +80,8 @@ export const agentMode: Mode = {
...context.inputs.disallowedTools, ...context.inputs.disallowedTools,
]; ];
// Export as INPUT_ prefixed variables for the base action core.exportVariable("ALLOWED_TOOLS", allowedTools.join(","));
core.exportVariable("INPUT_ALLOWED_TOOLS", allowedTools.join(",")); core.exportVariable("DISALLOWED_TOOLS", disallowedTools.join(","));
core.exportVariable("INPUT_DISALLOWED_TOOLS", disallowedTools.join(","));
// Agent mode uses a minimal MCP configuration // Agent mode uses a minimal MCP configuration
// We don't need comment servers or PR-specific tools for automation // We don't need comment servers or PR-specific tools for automation

View File

@@ -297,9 +297,8 @@ This ensures users get value from the review even before checking individual inl
...context.inputs.disallowedTools, ...context.inputs.disallowedTools,
]; ];
// Export as INPUT_ prefixed variables for the base action core.exportVariable("ALLOWED_TOOLS", allowedTools.join(","));
core.exportVariable("INPUT_ALLOWED_TOOLS", allowedTools.join(",")); core.exportVariable("DISALLOWED_TOOLS", disallowedTools.join(","));
core.exportVariable("INPUT_DISALLOWED_TOOLS", disallowedTools.join(","));
const additionalMcpConfig = process.env.MCP_CONFIG || ""; const additionalMcpConfig = process.env.MCP_CONFIG || "";
const mcpConfig = await prepareMcpConfig({ const mcpConfig = await prepareMcpConfig({

96
test/actor.test.ts Normal file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import { checkHumanActor } from "../src/github/validation/actor";
import type { Octokit } from "@octokit/rest";
import { createMockContext } from "./mockContext";
function createMockOctokit(userType: string): Octokit {
return {
users: {
getByUsername: async () => ({
data: {
type: userType,
},
}),
},
} as unknown as Octokit;
}
describe("checkHumanActor", () => {
test("should pass for human actor", async () => {
const mockOctokit = createMockOctokit("User");
const context = createMockContext();
context.actor = "human-user";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should throw error for bot actor when not allowed", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "test-bot[bot]";
context.inputs.allowedBots = "";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: test-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
test("should pass for bot actor when all bots allowed", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "test-bot[bot]";
context.inputs.allowedBots = "*";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should pass for specific bot when in allowed list", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "dependabot[bot]";
context.inputs.allowedBots = "dependabot[bot],renovate[bot]";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should pass for specific bot when in allowed list (without [bot])", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "dependabot[bot]";
context.inputs.allowedBots = "dependabot,renovate";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should throw error for bot not in allowed list", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "other-bot[bot]";
context.inputs.allowedBots = "dependabot[bot],renovate[bot]";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: other-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
test("should throw error for bot not in allowed list (without [bot])", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "other-bot[bot]";
context.inputs.allowedBots = "dependabot,renovate";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: other-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
});

View File

@@ -37,6 +37,7 @@ describe("prepareMcpConfig", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}; };

View File

@@ -28,6 +28,7 @@ const defaultInputs = {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map<string, string>(), additionalPermissions: new Map<string, string>(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}; };
const defaultRepository = { const defaultRepository = {

View File

@@ -1,15 +1,29 @@
import { describe, test, expect, beforeEach } from "bun:test"; import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
import { agentMode } from "../../src/modes/agent"; import { agentMode } from "../../src/modes/agent";
import type { GitHubContext } from "../../src/github/context"; import type { GitHubContext } from "../../src/github/context";
import { createMockContext, createMockAutomationContext } from "../mockContext"; import { createMockContext, createMockAutomationContext } from "../mockContext";
import * as core from "@actions/core";
describe("Agent Mode", () => { describe("Agent Mode", () => {
let mockContext: GitHubContext; let mockContext: GitHubContext;
let exportVariableSpy: any;
let setOutputSpy: any;
beforeEach(() => { beforeEach(() => {
mockContext = createMockAutomationContext({ mockContext = createMockAutomationContext({
eventName: "workflow_dispatch", eventName: "workflow_dispatch",
}); });
exportVariableSpy = spyOn(core, "exportVariable").mockImplementation(
() => {},
);
setOutputSpy = spyOn(core, "setOutput").mockImplementation(() => {});
});
afterEach(() => {
exportVariableSpy?.mockClear();
setOutputSpy?.mockClear();
exportVariableSpy?.mockRestore();
setOutputSpy?.mockRestore();
}); });
test("agent mode has correct properties", () => { test("agent mode has correct properties", () => {
@@ -56,4 +70,67 @@ describe("Agent Mode", () => {
expect(agentMode.shouldTrigger(context)).toBe(false); expect(agentMode.shouldTrigger(context)).toBe(false);
}); });
}); });
test("prepare method sets up tools environment variables correctly", async () => {
// Clear any previous calls before this test
exportVariableSpy.mockClear();
setOutputSpy.mockClear();
const contextWithCustomTools = createMockAutomationContext({
eventName: "workflow_dispatch",
});
contextWithCustomTools.inputs.allowedTools = ["CustomTool1", "CustomTool2"];
contextWithCustomTools.inputs.disallowedTools = ["BadTool"];
const mockOctokit = {} as any;
const result = await agentMode.prepare({
context: contextWithCustomTools,
octokit: mockOctokit,
githubToken: "test-token",
});
// Verify that both ALLOWED_TOOLS and DISALLOWED_TOOLS are set
expect(exportVariableSpy).toHaveBeenCalledWith(
"ALLOWED_TOOLS",
"Edit,MultiEdit,Glob,Grep,LS,Read,Write,CustomTool1,CustomTool2",
);
expect(exportVariableSpy).toHaveBeenCalledWith(
"DISALLOWED_TOOLS",
"WebSearch,WebFetch,BadTool",
);
// Verify MCP config is set
expect(setOutputSpy).toHaveBeenCalledWith("mcp_config", expect.any(String));
// Verify return structure
expect(result).toEqual({
commentId: undefined,
branchInfo: {
baseBranch: "",
currentBranch: "",
claudeBranch: undefined,
},
mcpConfig: expect.any(String),
});
});
test("prepare method creates prompt file with correct content", async () => {
const contextWithPrompts = createMockAutomationContext({
eventName: "workflow_dispatch",
});
contextWithPrompts.inputs.overridePrompt = "Custom override prompt";
contextWithPrompts.inputs.directPrompt =
"Direct prompt (should be ignored)";
const mockOctokit = {} as any;
await agentMode.prepare({
context: contextWithPrompts,
octokit: mockOctokit,
githubToken: "test-token",
});
// Note: We can't easily test file creation in this unit test,
// but we can verify the method completes without errors
expect(setOutputSpy).toHaveBeenCalledWith("mcp_config", expect.any(String));
});
}); });

View File

@@ -73,6 +73,7 @@ describe("checkWritePermissions", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
@@ -126,6 +127,16 @@ describe("checkWritePermissions", () => {
); );
}); });
test("should return true for bot user", async () => {
const mockOctokit = createMockOctokit("none");
const context = createContext();
context.actor = "test-bot[bot]";
const result = await checkWritePermissions(mockOctokit, context);
expect(result).toBe(true);
});
test("should throw error when permission check fails", async () => { test("should throw error when permission check fails", async () => {
const error = new Error("API error"); const error = new Error("API error");
const mockOctokit = { const mockOctokit = {

View File

@@ -41,6 +41,7 @@ describe("checkContainsTrigger", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -74,6 +75,7 @@ describe("checkContainsTrigger", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(false); expect(checkContainsTrigger(context)).toBe(false);
@@ -291,6 +293,7 @@ describe("checkContainsTrigger", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -325,6 +328,7 @@ describe("checkContainsTrigger", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -359,6 +363,7 @@ describe("checkContainsTrigger", () => {
useStickyComment: false, useStickyComment: false,
additionalPermissions: new Map(), additionalPermissions: new Map(),
useCommitSigning: false, useCommitSigning: false,
allowedBots: "",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(false); expect(checkContainsTrigger(context)).toBe(false);