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:
|
with:
|
||||||
prompt: "List all available tools"
|
prompt: "List all available tools"
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
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:
|
env:
|
||||||
# Change to test directory so it finds .mcp.json
|
# Change to test directory so it finds .mcp.json
|
||||||
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
|
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
|
||||||
@ -108,7 +110,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prompt: "List all available tools"
|
prompt: "List all available tools"
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
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:
|
env:
|
||||||
# Change to test directory so bun can find the MCP server script
|
# Change to test directory so bun can find the MCP server script
|
||||||
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
|
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"
|
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
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
|
# Auth configuration
|
||||||
anthropic_api_key:
|
anthropic_api_key:
|
||||||
@ -279,6 +283,7 @@ runs:
|
|||||||
# Base-action inputs
|
# Base-action inputs
|
||||||
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||||
|
INPUT_SETTING_SOURCES: ${{ inputs.setting_sources }}
|
||||||
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
|
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_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_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 | '' |
|
| `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 | '' |
|
| `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 | '' |
|
| `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 | '' |
|
| `system_prompt` | Override system prompt | No | '' |
|
||||||
| `append_system_prompt` | Append to 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 | '' |
|
| `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.
|
\*\*`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
|
## Outputs
|
||||||
|
|
||||||
| Output | Description |
|
| Output | Description |
|
||||||
|
|||||||
@ -18,6 +18,10 @@ inputs:
|
|||||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
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
|
# Action settings
|
||||||
claude_args:
|
claude_args:
|
||||||
@ -165,6 +169,7 @@ runs:
|
|||||||
INPUT_PROMPT: ${{ inputs.prompt }}
|
INPUT_PROMPT: ${{ inputs.prompt }}
|
||||||
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
|
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
|
||||||
INPUT_SETTINGS: ${{ inputs.settings }}
|
INPUT_SETTINGS: ${{ inputs.settings }}
|
||||||
|
INPUT_SETTING_SOURCES: ${{ inputs.setting_sources }}
|
||||||
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
|
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
|
||||||
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
|
||||||
|
|||||||
@ -48,6 +48,7 @@ async function run() {
|
|||||||
model: process.env.ANTHROPIC_MODEL,
|
model: process.env.ANTHROPIC_MODEL,
|
||||||
pathToClaudeCodeExecutable: claudeExecutable,
|
pathToClaudeCodeExecutable: claudeExecutable,
|
||||||
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
||||||
|
settingSources: process.env.INPUT_SETTING_SOURCES,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set outputs for the standalone base-action
|
// Set outputs for the standalone base-action
|
||||||
|
|||||||
@ -271,13 +271,22 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
|
|||||||
extraArgs,
|
extraArgs,
|
||||||
env,
|
env,
|
||||||
|
|
||||||
// Load settings from sources - prefer user's --setting-sources if provided, otherwise use all sources
|
// Setting sources precedence: direct input > --setting-sources in claude_args > default.
|
||||||
// This ensures users can override the default behavior (e.g., --setting-sources user to avoid in-repo configs)
|
// The default is supplied by the caller (the wrapper action passes
|
||||||
settingSources: extraArgs["setting-sources"]
|
// ["user","project","local"]); base-action applies an event-gated default of ["user"]
|
||||||
? (extraArgs["setting-sources"].split(
|
// under pull_request_target/workflow_run/issue_comment and ["user","project","local"]
|
||||||
",",
|
// otherwise. Both action.yml files leave the YAML default empty so that
|
||||||
) as SdkOptions["settingSources"])
|
// --setting-sources in claude_args is reachable when the input is not set.
|
||||||
: ["user", "project", "local"],
|
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
|
// Remove setting-sources from extraArgs to avoid passing it twice
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { runClaudeWithSdk } from "./run-claude-sdk";
|
import { runClaudeWithSdk } from "./run-claude-sdk";
|
||||||
import type { ClaudeRunResult } from "./run-claude-sdk";
|
import type { ClaudeRunResult } from "./run-claude-sdk";
|
||||||
import { parseSdkOptions } from "./parse-sdk-options";
|
import { parseSdkOptions } from "./parse-sdk-options";
|
||||||
|
import type { Options as SdkOptions } from "@anthropic-ai/claude-agent-sdk";
|
||||||
|
|
||||||
export type ClaudeOptions = {
|
export type ClaudeOptions = {
|
||||||
claudeArgs?: string;
|
claudeArgs?: string;
|
||||||
@ -14,6 +15,8 @@ export type ClaudeOptions = {
|
|||||||
appendSystemPrompt?: string;
|
appendSystemPrompt?: string;
|
||||||
fallbackModel?: string;
|
fallbackModel?: string;
|
||||||
showFullOutput?: string;
|
showFullOutput?: string;
|
||||||
|
settingSources?: string;
|
||||||
|
defaultSettingSources?: SdkOptions["settingSources"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function runClaude(
|
export async function runClaude(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/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 { parseSdkOptions } from "../src/parse-sdk-options";
|
||||||
import type { ClaudeOptions } from "../src/run-claude";
|
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
|
// 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
|
// fetches it from GraphQL; agent mode on issue_comment is an edge case
|
||||||
// that at worst restores from the wrong trusted branch (still secure).
|
// that at worst restores from the wrong trusted branch (still secure).
|
||||||
|
let configRestoredFromBase = false;
|
||||||
if (isEntityContext(context) && context.isPR) {
|
if (isEntityContext(context) && context.isPR) {
|
||||||
let restoreBase = baseBranch;
|
let restoreBase = baseBranch;
|
||||||
if (
|
if (
|
||||||
@ -253,6 +254,7 @@ async function run() {
|
|||||||
}
|
}
|
||||||
if (restoreBase) {
|
if (restoreBase) {
|
||||||
restoreConfigFromBase(restoreBase);
|
restoreConfigFromBase(restoreBase);
|
||||||
|
configRestoredFromBase = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +280,13 @@ async function run() {
|
|||||||
model: process.env.ANTHROPIC_MODEL,
|
model: process.env.ANTHROPIC_MODEL,
|
||||||
pathToClaudeCodeExecutable: claudeExecutable,
|
pathToClaudeCodeExecutable: claudeExecutable,
|
||||||
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
|
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";
|
claudeSuccess = claudeResult.conclusion === "success";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user