mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
* feat: add ssh_signing_key input for SSH commit signing Add a new ssh_signing_key input that allows passing an SSH signing key for commit signing, as an alternative to the existing use_commit_signing (which uses GitHub API-based commits). When ssh_signing_key is provided: - Git is configured to use SSH signing (gpg.format=ssh, commit.gpgsign=true) - The key is written to ~/.ssh/claude_signing_key with 0600 permissions - Git CLI commands are used (not MCP file ops) - The key is cleaned up in a post step for security Behavior matrix: | ssh_signing_key | use_commit_signing | Result | |-----------------|-------------------|--------| | not set | false | Regular git, no signing | | not set | true | GitHub API (MCP), verified commits | | set | false | Git CLI with SSH signing | | set | true | Git CLI with SSH signing (ssh_signing_key takes precedence) * docs: add SSH signing key documentation - Update security.md with detailed setup instructions for both signing options - Explain that ssh_signing_key enables full git CLI operations (rebasing, etc.) - Add ssh_signing_key to inputs table in usage.md - Update bot_id/bot_name descriptions to note they're needed for verified commits * fix: address security review feedback for SSH signing - Write SSH key atomically with mode 0o600 (fixes TOCTOU race condition) - Create .ssh directory with mode 0o700 (SSH best practices) - Add input validation for SSH key format - Remove unused chmod import - Add tests for validation logic
262 lines
7.8 KiB
TypeScript
262 lines
7.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { detectMode } from "../../src/modes/detector";
|
|
import type { GitHubContext } from "../../src/github/context";
|
|
|
|
describe("detectMode with enhanced routing", () => {
|
|
const baseContext = {
|
|
runId: "test-run",
|
|
eventAction: "opened",
|
|
repository: {
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
full_name: "test-owner/test-repo",
|
|
},
|
|
actor: "test-user",
|
|
inputs: {
|
|
prompt: "",
|
|
triggerPhrase: "@claude",
|
|
assigneeTrigger: "",
|
|
labelTrigger: "",
|
|
branchPrefix: "claude/",
|
|
useStickyComment: false,
|
|
useCommitSigning: false,
|
|
sshSigningKey: "",
|
|
botId: "123456",
|
|
botName: "claude-bot",
|
|
allowedBots: "",
|
|
allowedNonWriteUsers: "",
|
|
trackProgress: false,
|
|
includeFixLinks: true,
|
|
},
|
|
};
|
|
|
|
describe("PR Events with track_progress", () => {
|
|
it("should use tag mode when track_progress is true for pull_request.opened", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request",
|
|
eventAction: "opened",
|
|
payload: { pull_request: { number: 1 } } as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
inputs: { ...baseContext.inputs, trackProgress: true },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
|
|
it("should use tag mode when track_progress is true for pull_request.synchronize", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request",
|
|
eventAction: "synchronize",
|
|
payload: { pull_request: { number: 1 } } as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
inputs: { ...baseContext.inputs, trackProgress: true },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
|
|
it("should use agent mode when track_progress is false for pull_request.opened", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request",
|
|
eventAction: "opened",
|
|
payload: { pull_request: { number: 1 } } as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
inputs: { ...baseContext.inputs, trackProgress: false },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("agent");
|
|
});
|
|
|
|
it("should throw error when track_progress is used with unsupported PR action", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request",
|
|
eventAction: "closed",
|
|
payload: { pull_request: { number: 1 } } as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
inputs: { ...baseContext.inputs, trackProgress: true },
|
|
};
|
|
|
|
expect(() => detectMode(context)).toThrow(
|
|
/track_progress for pull_request events is only supported for actions/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Issue Events with track_progress", () => {
|
|
it("should use tag mode when track_progress is true for issues.opened", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issues",
|
|
eventAction: "opened",
|
|
payload: { issue: { number: 1, body: "Test" } } as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: { ...baseContext.inputs, trackProgress: true },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
|
|
it("should use agent mode when track_progress is false for issues", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issues",
|
|
eventAction: "opened",
|
|
payload: { issue: { number: 1, body: "Test" } } as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: { ...baseContext.inputs, trackProgress: false },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("agent");
|
|
});
|
|
|
|
it("should use agent mode for issues with explicit prompt", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issues",
|
|
eventAction: "opened",
|
|
payload: { issue: { number: 1, body: "Test issue" } } as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: { ...baseContext.inputs, prompt: "Analyze this issue" },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("agent");
|
|
});
|
|
|
|
it("should use tag mode for issues with @claude mention and no prompt", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issues",
|
|
eventAction: "opened",
|
|
payload: { issue: { number: 1, body: "@claude help" } } as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
});
|
|
|
|
describe("Comment Events (unchanged behavior)", () => {
|
|
it("should use tag mode for issue_comment with @claude mention", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issue_comment",
|
|
payload: {
|
|
issue: { number: 1, body: "Test" },
|
|
comment: { body: "@claude help" },
|
|
} as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
|
|
it("should use agent mode for issue_comment with prompt provided", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issue_comment",
|
|
payload: {
|
|
issue: { number: 1, body: "Test" },
|
|
comment: { body: "@claude help" },
|
|
} as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: { ...baseContext.inputs, prompt: "Review this PR" },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("agent");
|
|
});
|
|
|
|
it("should use tag mode for PR review comments with @claude mention", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request_review_comment",
|
|
payload: {
|
|
pull_request: { number: 1, body: "Test" },
|
|
comment: { body: "@claude check this" },
|
|
} as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
});
|
|
|
|
describe("Automation Events (should error with track_progress)", () => {
|
|
it("should throw error when track_progress is used with workflow_dispatch", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "workflow_dispatch",
|
|
payload: {} as any,
|
|
inputs: { ...baseContext.inputs, trackProgress: true },
|
|
};
|
|
|
|
expect(() => detectMode(context)).toThrow(
|
|
/track_progress is only supported /,
|
|
);
|
|
});
|
|
|
|
it("should use agent mode for workflow_dispatch without track_progress", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "workflow_dispatch",
|
|
payload: {} as any,
|
|
inputs: { ...baseContext.inputs, prompt: "Run workflow" },
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("agent");
|
|
});
|
|
});
|
|
|
|
describe("Custom prompt injection in tag mode", () => {
|
|
it("should use tag mode for PR events when both track_progress and prompt are provided", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "pull_request",
|
|
eventAction: "opened",
|
|
payload: { pull_request: { number: 1 } } as any,
|
|
entityNumber: 1,
|
|
isPR: true,
|
|
inputs: {
|
|
...baseContext.inputs,
|
|
trackProgress: true,
|
|
prompt: "Review for security issues",
|
|
},
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
|
|
it("should use tag mode for issue events when both track_progress and prompt are provided", () => {
|
|
const context: GitHubContext = {
|
|
...baseContext,
|
|
eventName: "issues",
|
|
eventAction: "opened",
|
|
payload: { issue: { number: 1, body: "Test" } } as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: {
|
|
...baseContext.inputs,
|
|
trackProgress: true,
|
|
prompt: "Analyze this issue",
|
|
},
|
|
};
|
|
|
|
expect(detectMode(context)).toBe("tag");
|
|
});
|
|
});
|
|
});
|