Compare commits
6 Commits
main
...
setting-so
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6dac6f121 | ||
|
|
044a1036f6 | ||
|
|
a551ae4682 | ||
|
|
12f457aad8 | ||
|
|
625ab08afd | ||
|
|
8dfb31d8a5 |
8
.github/workflows/test-mcp-servers.yml
vendored
8
.github/workflows/test-mcp-servers.yml
vendored
@ -27,6 +27,8 @@ jobs:
|
||||
with:
|
||||
prompt: "List all available tools"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
# Explicitly include project so .mcp.json is discovered regardless of the event-gated default
|
||||
setting_sources: "user,project"
|
||||
env:
|
||||
# Change to test directory so it finds .mcp.json
|
||||
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
|
||||
@ -108,7 +110,11 @@ jobs:
|
||||
with:
|
||||
prompt: "List all available tools"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
mcp_config: '{"mcpServers":{"test-server":{"type":"stdio","command":"bun","args":["simple-mcp-server.ts"],"env":{}}}}'
|
||||
# mcp_config input was removed; pass via claude_args. Pin setting_sources to "user"
|
||||
# so .mcp.json is NOT auto-discovered — this proves the flag itself works.
|
||||
setting_sources: "user"
|
||||
claude_args: >-
|
||||
--mcp-config '{"mcpServers":{"test-server":{"type":"stdio","command":"bun","args":["simple-mcp-server.ts"],"env":{}}}}'
|
||||
env:
|
||||
# Change to test directory so bun can find the MCP server script
|
||||
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
|
||||
|
||||
@ -62,6 +62,10 @@ inputs:
|
||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||
required: false
|
||||
default: ""
|
||||
setting_sources:
|
||||
description: "Comma-separated list of setting sources to load (user, project, local). When unset, the action applies 'user,project,local' at runtime for PR contexts where .claude/ is restored from the base branch; for other contexts it applies the same event-gated default as base-action. Set to 'user' to ignore in-repo settings entirely."
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Auth configuration
|
||||
anthropic_api_key:
|
||||
@ -279,6 +283,7 @@ runs:
|
||||
# Base-action inputs
|
||||
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||
INPUT_SETTING_SOURCES: ${{ inputs.setting_sources }}
|
||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
||||
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
||||
|
||||
@ -94,6 +94,7 @@ Add the following to your workflow file:
|
||||
| `max_turns` | Maximum number of conversation turns (default: no limit) | No | '' |
|
||||
| `mcp_config` | Path to the MCP configuration JSON file, or MCP configuration JSON string | No | '' |
|
||||
| `settings` | Path to Claude Code settings JSON file, or settings JSON string | No | '' |
|
||||
| `setting_sources` | Comma-separated setting sources to load (`user`, `project`, `local`). Project/local merge permissions additively. | No | event-dependent (see below) |
|
||||
| `system_prompt` | Override system prompt | No | '' |
|
||||
| `append_system_prompt` | Append to system prompt | No | '' |
|
||||
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML multiline format) | No | '' |
|
||||
@ -111,6 +112,8 @@ Add the following to your workflow file:
|
||||
|
||||
\*\*`show_full_output` is automatically enabled when GitHub Actions debug mode is active. See [security documentation](../docs/security.md#️-full-output-security-warning) for important security considerations.
|
||||
|
||||
`setting_sources` defaults to `user,project,local` for most events. Under `pull_request_target`, `workflow_run`, and `issue_comment` it defaults to `user` only; set it explicitly if you want project/local settings to load for those events.
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
|
||||
@ -18,6 +18,10 @@ inputs:
|
||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||
required: false
|
||||
default: ""
|
||||
setting_sources:
|
||||
description: "Comma-separated list of setting sources to load (user, project, local). Defaults to 'user,project,local'; under pull_request_target/workflow_run/issue_comment, defaults to 'user' only. Project/local settings additively merge permissions with allowed_tools — set explicitly to control which sources load."
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Action settings
|
||||
claude_args:
|
||||
@ -165,6 +169,7 @@ runs:
|
||||
INPUT_PROMPT: ${{ inputs.prompt }}
|
||||
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
|
||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||
INPUT_SETTING_SOURCES: ${{ inputs.setting_sources }}
|
||||
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
|
||||
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
||||
|
||||
@ -48,6 +48,7 @@ async function run() {
|
||||
model: process.env.ANTHROPIC_MODEL,
|
||||
pathToClaudeCodeExecutable: claudeExecutable,
|
||||
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
||||
settingSources: process.env.INPUT_SETTING_SOURCES,
|
||||
});
|
||||
|
||||
// Set outputs for the standalone base-action
|
||||
|
||||
@ -271,13 +271,22 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
||||
extraArgs,
|
||||
env,
|
||||
|
||||
// Load settings from sources - prefer user's --setting-sources if provided, otherwise use all sources
|
||||
// This ensures users can override the default behavior (e.g., --setting-sources user to avoid in-repo configs)
|
||||
settingSources: extraArgs["setting-sources"]
|
||||
? (extraArgs["setting-sources"].split(
|
||||
",",
|
||||
) as SdkOptions["settingSources"])
|
||||
: ["user", "project", "local"],
|
||||
// Setting sources precedence: direct input > --setting-sources in claude_args > default.
|
||||
// The default is supplied by the caller (the wrapper action passes
|
||||
// ["user","project","local"]); base-action applies an event-gated default of ["user"]
|
||||
// under pull_request_target/workflow_run/issue_comment and ["user","project","local"]
|
||||
// otherwise. Both action.yml files leave the YAML default empty so that
|
||||
// --setting-sources in claude_args is reachable when the input is not set.
|
||||
settingSources: (options.settingSources
|
||||
? options.settingSources.split(",").map((s) => s.trim())
|
||||
: extraArgs["setting-sources"]
|
||||
? extraArgs["setting-sources"].split(",").map((s) => s.trim())
|
||||
: (options.defaultSettingSources ??
|
||||
(process.env.GITHUB_EVENT_NAME === "pull_request_target" ||
|
||||
process.env.GITHUB_EVENT_NAME === "workflow_run" ||
|
||||
process.env.GITHUB_EVENT_NAME === "issue_comment"
|
||||
? ["user"]
|
||||
: ["user", "project", "local"]))) as SdkOptions["settingSources"],
|
||||
};
|
||||
|
||||
// Remove setting-sources from extraArgs to avoid passing it twice
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { runClaudeWithSdk } from "./run-claude-sdk";
|
||||
import type { ClaudeRunResult } from "./run-claude-sdk";
|
||||
import { parseSdkOptions } from "./parse-sdk-options";
|
||||
import type { Options as SdkOptions } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
export type ClaudeOptions = {
|
||||
claudeArgs?: string;
|
||||
@ -14,6 +15,8 @@ export type ClaudeOptions = {
|
||||
appendSystemPrompt?: string;
|
||||
fallbackModel?: string;
|
||||
showFullOutput?: string;
|
||||
settingSources?: string;
|
||||
defaultSettingSources?: SdkOptions["settingSources"];
|
||||
};
|
||||
|
||||
export async function runClaude(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { describe, test, expect, afterEach } from "bun:test";
|
||||
import { parseSdkOptions } from "../src/parse-sdk-options";
|
||||
import type { ClaudeOptions } from "../src/run-claude";
|
||||
|
||||
@ -422,4 +422,129 @@ describe("parseSdkOptions", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("settingSources", () => {
|
||||
const originalEventName = process.env.GITHUB_EVENT_NAME;
|
||||
afterEach(() => {
|
||||
if (originalEventName === undefined) {
|
||||
delete process.env.GITHUB_EVENT_NAME;
|
||||
} else {
|
||||
process.env.GITHUB_EVENT_NAME = originalEventName;
|
||||
}
|
||||
});
|
||||
|
||||
test("should default to ['user','project','local'] for non-gated events", () => {
|
||||
process.env.GITHUB_EVENT_NAME = "push";
|
||||
const result = parseSdkOptions({});
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual([
|
||||
"user",
|
||||
"project",
|
||||
"local",
|
||||
]);
|
||||
});
|
||||
|
||||
test("should default to ['user'] under pull_request_target", () => {
|
||||
process.env.GITHUB_EVENT_NAME = "pull_request_target";
|
||||
const result = parseSdkOptions({});
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user"]);
|
||||
});
|
||||
|
||||
test("should default to ['user'] under workflow_run", () => {
|
||||
process.env.GITHUB_EVENT_NAME = "workflow_run";
|
||||
const result = parseSdkOptions({});
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user"]);
|
||||
});
|
||||
|
||||
test("should default to ['user'] under issue_comment", () => {
|
||||
process.env.GITHUB_EVENT_NAME = "issue_comment";
|
||||
const result = parseSdkOptions({});
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user"]);
|
||||
});
|
||||
|
||||
test("should use direct settingSources input when provided", () => {
|
||||
const options: ClaudeOptions = {
|
||||
settingSources: "user,project,local",
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual([
|
||||
"user",
|
||||
"project",
|
||||
"local",
|
||||
]);
|
||||
});
|
||||
|
||||
test("should use --setting-sources from claudeArgs when no direct input", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: "--setting-sources user,project",
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user", "project"]);
|
||||
expect(result.sdkOptions.extraArgs?.["setting-sources"]).toBeUndefined();
|
||||
});
|
||||
|
||||
test("direct input should take precedence over claudeArgs", () => {
|
||||
const options: ClaudeOptions = {
|
||||
settingSources: "user",
|
||||
claudeArgs: "--setting-sources user,project,local",
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user"]);
|
||||
});
|
||||
|
||||
test("should trim whitespace in comma-separated values", () => {
|
||||
const options: ClaudeOptions = {
|
||||
settingSources: "user, project , local",
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual([
|
||||
"user",
|
||||
"project",
|
||||
"local",
|
||||
]);
|
||||
});
|
||||
|
||||
test("explicit defaultSettingSources overrides the event-gated default", () => {
|
||||
process.env.GITHUB_EVENT_NAME = "pull_request_target";
|
||||
const options: ClaudeOptions = {
|
||||
defaultSettingSources: ["user", "project", "local"],
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual([
|
||||
"user",
|
||||
"project",
|
||||
"local",
|
||||
]);
|
||||
});
|
||||
|
||||
test("--setting-sources in claudeArgs should win over defaultSettingSources", () => {
|
||||
const options: ClaudeOptions = {
|
||||
claudeArgs: "--setting-sources user",
|
||||
defaultSettingSources: ["user", "project", "local"],
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user"]);
|
||||
});
|
||||
|
||||
test("empty-string settingSources falls through to claudeArgs then default", () => {
|
||||
// YAML default: "" — INPUT_SETTING_SOURCES is "" when the user doesn't set the input
|
||||
const options: ClaudeOptions = {
|
||||
settingSources: "",
|
||||
claudeArgs: "--setting-sources user,project",
|
||||
defaultSettingSources: ["user", "project", "local"],
|
||||
};
|
||||
const result = parseSdkOptions(options);
|
||||
|
||||
expect(result.sdkOptions.settingSources).toEqual(["user", "project"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -241,6 +241,7 @@ async function run() {
|
||||
// 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).
|
||||
let configRestoredFromBase = false;
|
||||
if (isEntityContext(context) && context.isPR) {
|
||||
let restoreBase = baseBranch;
|
||||
if (
|
||||
@ -253,6 +254,7 @@ async function run() {
|
||||
}
|
||||
if (restoreBase) {
|
||||
restoreConfigFromBase(restoreBase);
|
||||
configRestoredFromBase = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,6 +280,13 @@ async function run() {
|
||||
model: process.env.ANTHROPIC_MODEL,
|
||||
pathToClaudeCodeExecutable: claudeExecutable,
|
||||
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
||||
settingSources: process.env.INPUT_SETTING_SOURCES,
|
||||
// Only assert that project/local config is safe to load when it was actually
|
||||
// restored from the base branch above. Otherwise leave undefined so
|
||||
// parseSdkOptions applies its event-gated default.
|
||||
defaultSettingSources: configRestoredFromBase
|
||||
? ["user", "project", "local"]
|
||||
: undefined,
|
||||
});
|
||||
|
||||
claudeSuccess = claudeResult.conclusion === "success";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user