From 1b4fc382c814b62c2e8ea8249a1a44f2c262ac7b Mon Sep 17 00:00:00 2001 From: km-anthropic Date: Fri, 8 Aug 2025 14:00:31 -0700 Subject: [PATCH] Simplify agent mode and re-add additional_permissions input - Agent mode now only triggers when explicit prompt is provided - Removed automatic triggering for workflow_dispatch/schedule without prompt - Re-added additional_permissions input for requesting GitHub permissions - Fixed TypeScript types for mock context helpers to properly handle partial inputs - Updated documentation to reflect simplified mode behavior --- CLAUDE.md | 4 ++-- action.yml | 5 +++++ docs/experimental.md | 12 ++++++------ src/modes/agent/index.ts | 15 +++++++-------- src/modes/detector.ts | 12 +++--------- src/modes/types.ts | 2 +- test/mockContext.ts | 26 +++++++++++++++++++------- test/modes/agent.test.ts | 40 ++++++++++++++++++++++++++++++++-------- 8 files changed, 75 insertions(+), 41 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 061e731..7834fc2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,7 +53,7 @@ Execution steps: #### Mode System (`src/modes/`) - **Tag Mode** (`tag/`): Responds to `@claude` mentions and issue assignments -- **Agent Mode** (`agent/`): Automated execution for workflow_dispatch and schedule events only +- **Agent Mode** (`agent/`): Direct execution when explicit prompt is provided - Extensible registry pattern in `modes/registry.ts` #### GitHub Integration (`src/github/`) @@ -118,7 +118,7 @@ src/ - Modes implement `Mode` interface with `shouldTrigger()` and `prepare()` methods - Registry validates mode compatibility with GitHub event types -- Agent mode only works with workflow_dispatch and schedule events +- Agent mode triggers when explicit prompt is provided ### Comment Threading diff --git a/action.yml b/action.yml index ae364b5..1024f95 100644 --- a/action.yml +++ b/action.yml @@ -65,6 +65,10 @@ inputs: description: "Additional arguments to pass directly to Claude CLI" required: false default: "" + additional_permissions: + description: "Additional GitHub permissions to request (e.g., 'actions: read')" + required: false + default: "" use_sticky_comment: description: "Use just one comment to deliver issue/PR comments" required: false @@ -119,6 +123,7 @@ runs: USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} + ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} - name: Install Base Action Dependencies if: steps.prepare.outputs.contains_trigger == 'true' diff --git a/docs/experimental.md b/docs/experimental.md index f593881..e541c9f 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -25,19 +25,19 @@ The traditional implementation mode that responds to @claude mentions, issue ass **Note: Agent mode is currently in active development and may undergo breaking changes.** -For automation with workflow_dispatch and scheduled events only. +For direct automation when an explicit prompt is provided. -- **Triggers**: Only works with `workflow_dispatch` and `schedule` events - does NOT work with PR/issue events -- **Features**: Perfect for scheduled tasks, works with `override_prompt` -- **Use case**: Maintenance tasks, automated reporting, scheduled checks +- **Triggers**: Works with any event when `prompt` input is provided +- **Features**: Direct execution without @claude mentions, no tracking comments +- **Use case**: Automated PR reviews, scheduled tasks, workflow automation ```yaml - uses: anthropics/claude-code-action@beta with: - mode: agent anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - override_prompt: | + prompt: | Check for outdated dependencies and create an issue if any are found. + # Mode is auto-detected when prompt is provided ``` ### Experimental Review Mode diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index 7a0bc06..dc64692 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -1,23 +1,22 @@ import * as core from "@actions/core"; import { mkdir, writeFile } from "fs/promises"; import type { Mode, ModeOptions, ModeResult } from "../types"; -import { isAutomationContext } from "../../github/context"; import type { PreparedContext } from "../../create-prompt/types"; /** * Agent mode implementation. * - * This mode is specifically designed for automation events (workflow_dispatch and schedule). - * It bypasses the standard trigger checking and comment tracking used by tag mode, - * making it ideal for scheduled tasks and manual workflow runs. + * This mode runs whenever an explicit prompt is provided in the workflow configuration. + * It bypasses the standard @claude mention checking and comment tracking used by tag mode, + * providing direct access to Claude Code for automation workflows. */ export const agentMode: Mode = { name: "agent", - description: "Automation mode for workflow_dispatch and schedule events", + description: "Direct automation mode for explicit prompts", shouldTrigger(context) { - // Only trigger for automation events - return isAutomationContext(context); + // Only trigger when an explicit prompt is provided + return !!context.inputs?.prompt; }, prepareContext(context) { @@ -41,7 +40,7 @@ export const agentMode: Mode = { }, async prepare({ context }: ModeOptions): Promise { - // Agent mode handles automation events (workflow_dispatch, schedule) only + // Agent mode handles automation events and any event with explicit prompts // TODO: handle by createPrompt (similar to tag and review modes) // Create prompt directory diff --git a/src/modes/detector.ts b/src/modes/detector.ts index 9601b0c..0d88b28 100644 --- a/src/modes/detector.ts +++ b/src/modes/detector.ts @@ -9,13 +9,7 @@ import { checkContainsTrigger } from "../github/validation/trigger"; export type AutoDetectedMode = "tag" | "agent"; export function detectMode(context: GitHubContext): AutoDetectedMode { - // If prompt is provided, always use agent mode - // Reasoning: When users provide explicit instructions via the prompt parameter, - // they want Claude to execute those instructions immediately without waiting for - // @claude mentions or other triggers. This aligns with the v1.0 philosophy where - // Claude Code handles everything - the GitHub Action is just a thin wrapper that - // passes through prompts directly to Claude Code for native handling (including - // slash commands). This provides the most direct and flexible interaction model. + // If prompt is provided, use agent mode for direct execution if (context.inputs?.prompt) { return "agent"; } @@ -38,7 +32,7 @@ export function detectMode(context: GitHubContext): AutoDetectedMode { } } - // Default to agent mode for everything else + // Default to agent mode (which won't trigger without a prompt) return "agent"; } @@ -47,7 +41,7 @@ export function getModeDescription(mode: AutoDetectedMode): string { case "tag": return "Interactive mode triggered by @claude mentions"; case "agent": - return "General automation mode for all events"; + return "Direct automation mode for explicit prompts"; default: return "Unknown mode"; } diff --git a/src/modes/types.ts b/src/modes/types.ts index c555980..1f5069a 100644 --- a/src/modes/types.ts +++ b/src/modes/types.ts @@ -26,7 +26,7 @@ export type ModeData = { * * Current modes include: * - 'tag': Interactive mode triggered by @claude mentions - * - 'agent': General automation mode for all events (default) + * - 'agent': Direct automation mode triggered by explicit prompts */ export type Mode = { name: ModeName; diff --git a/test/mockContext.ts b/test/mockContext.ts index d664aae..973cb60 100644 --- a/test/mockContext.ts +++ b/test/mockContext.ts @@ -27,8 +27,12 @@ const defaultRepository = { full_name: "test-owner/test-repo", }; +type MockContextOverrides = Omit, 'inputs'> & { + inputs?: Partial; +}; + export const createMockContext = ( - overrides: Partial = {}, + overrides: MockContextOverrides = {}, ): ParsedGitHubContext => { const baseContext: ParsedGitHubContext = { runId: "1234567890", @@ -42,15 +46,19 @@ export const createMockContext = ( inputs: defaultInputs, }; - if (overrides.inputs) { - overrides.inputs = { ...defaultInputs, ...overrides.inputs }; - } + const mergedInputs = overrides.inputs + ? { ...defaultInputs, ...overrides.inputs } + : defaultInputs; - return { ...baseContext, ...overrides }; + return { ...baseContext, ...overrides, inputs: mergedInputs }; +}; + +type MockAutomationOverrides = Omit, 'inputs'> & { + inputs?: Partial; }; export const createMockAutomationContext = ( - overrides: Partial = {}, + overrides: MockAutomationOverrides = {}, ): AutomationContext => { const baseContext: AutomationContext = { runId: "1234567890", @@ -62,7 +70,11 @@ export const createMockAutomationContext = ( inputs: defaultInputs, }; - return { ...baseContext, ...overrides }; + const mergedInputs = overrides.inputs + ? { ...defaultInputs, ...overrides.inputs } + : defaultInputs; + + return { ...baseContext, ...overrides, inputs: mergedInputs }; }; export const mockIssueOpenedContext: ParsedGitHubContext = { diff --git a/test/modes/agent.test.ts b/test/modes/agent.test.ts index 2daf068..090c894 100644 --- a/test/modes/agent.test.ts +++ b/test/modes/agent.test.ts @@ -15,7 +15,7 @@ describe("Agent Mode", () => { test("agent mode has correct properties", () => { expect(agentMode.name).toBe("agent"); expect(agentMode.description).toBe( - "Automation mode for workflow_dispatch and schedule events", + "Direct automation mode for explicit prompts", ); expect(agentMode.shouldCreateTrackingComment()).toBe(false); expect(agentMode.getAllowedTools()).toEqual([]); @@ -31,19 +31,19 @@ describe("Agent Mode", () => { expect(Object.keys(context)).toEqual(["mode", "githubContext"]); }); - test("agent mode only triggers for workflow_dispatch and schedule events", () => { - // Should trigger for automation events + test("agent mode only triggers when prompt is provided", () => { + // Should NOT trigger for automation events without prompt const workflowDispatchContext = createMockAutomationContext({ eventName: "workflow_dispatch", }); - expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(true); + expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(false); const scheduleContext = createMockAutomationContext({ eventName: "schedule", }); - expect(agentMode.shouldTrigger(scheduleContext)).toBe(true); + expect(agentMode.shouldTrigger(scheduleContext)).toBe(false); - // Should NOT trigger for entity events + // Should NOT trigger for entity events without prompt const entityEvents = [ "issue_comment", "pull_request", @@ -52,8 +52,32 @@ describe("Agent Mode", () => { ] as const; entityEvents.forEach((eventName) => { - const context = createMockContext({ eventName }); - expect(agentMode.shouldTrigger(context)).toBe(false); + const contextNoPrompt = createMockContext({ eventName }); + expect(agentMode.shouldTrigger(contextNoPrompt)).toBe(false); + }); + + // Should trigger for ANY event when prompt is provided + const allEvents = [ + "workflow_dispatch", + "schedule", + "issue_comment", + "pull_request", + "pull_request_review", + "issues", + ] as const; + + allEvents.forEach((eventName) => { + const contextWithPrompt = + eventName === "workflow_dispatch" || eventName === "schedule" + ? createMockAutomationContext({ + eventName, + inputs: { prompt: "Do something" }, + }) + : createMockContext({ + eventName, + inputs: { prompt: "Do something" }, + }); + expect(agentMode.shouldTrigger(contextWithPrompt)).toBe(true); }); }); });