Compare commits

..

6 Commits

Author SHA1 Message Date
Ashwin Bhat
9f02f6f6d4 fix: Increase maxBuffer for jq processing to handle large Claude outputs (#473)
Fixes "stdout maxBuffer length exceeded" error by increasing the buffer
from Node.js default of 1MB to 10MB when processing Claude output with jq.
This prevents failures when Claude produces large execution logs.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-20 20:02:00 -07:00
GitHub Actions
79cee96324 chore: bump Claude Code version to 1.0.86 2025-08-20 23:27:37 +00:00
Chris Lloyd
194fca8b05 feat: preserve file permissions when committing via GitHub API (#469)
Add file permission detection to github-file-ops-server.ts to properly preserve file modes (regular, executable, symlink) when committing files through the GitHub API. This ensures executable scripts and other special files maintain their correct permissions.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-19 17:18:05 -07:00
GitHub Actions
0f913a6e0e chore: bump Claude Code version to 1.0.85 2025-08-19 23:59:52 +00:00
Ashwin Bhat
68b7ca379c include input bools in claude env (#464) 2025-08-18 17:00:18 -07:00
GitHub Actions
900322ca88 chore: bump Claude Code version to 1.0.84 2025-08-18 23:43:42 +00:00
6 changed files with 118 additions and 11 deletions

View File

@@ -166,6 +166,7 @@ runs:
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
ALL_INPUTS: ${{ toJson(inputs) }}
- name: Install Base Action Dependencies - name: Install Base Action Dependencies
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'
@@ -177,7 +178,7 @@ runs:
echo "Base-action dependencies installed" echo "Base-action dependencies installed"
cd - cd -
# Install Claude Code globally # Install Claude Code globally
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 curl -fsSL https://claude.ai/install.sh | bash -s 1.0.86
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Setup Network Restrictions - name: Setup Network Restrictions
@@ -212,6 +213,7 @@ runs:
INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} INPUT_CLAUDE_ENV: ${{ inputs.claude_env }}
INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }}
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }}
# Model configuration # Model configuration
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }} ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}

View File

@@ -118,7 +118,7 @@ runs:
- name: Install Claude Code - name: Install Claude Code
shell: bash shell: bash
run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.83 run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.86
- name: Run Claude Code Action - name: Run Claude Code Action
shell: bash shell: bash

View File

@@ -110,6 +110,10 @@ export function prepareRunConfig(
// Parse custom environment variables // Parse custom environment variables
const customEnv = parseCustomEnvVars(options.claudeEnv); const customEnv = parseCustomEnvVars(options.claudeEnv);
if (process.env.INPUT_ACTION_INPUTS_PRESENT) {
customEnv.GITHUB_ACTION_INPUTS = process.env.INPUT_ACTION_INPUTS_PRESENT;
}
return { return {
claudeArgs, claudeArgs,
promptPath, promptPath,
@@ -142,9 +146,11 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
console.log(`Prompt file size: ${promptSize} bytes`); console.log(`Prompt file size: ${promptSize} bytes`);
// Log custom environment variables if any // Log custom environment variables if any
if (Object.keys(config.env).length > 0) { const customEnvKeys = Object.keys(config.env).filter(
const envKeys = Object.keys(config.env).join(", "); (key) => key !== "CLAUDE_ACTION_INPUTS_PRESENT",
console.log(`Custom environment variables: ${envKeys}`); );
if (customEnvKeys.length > 0) {
console.log(`Custom environment variables: ${customEnvKeys.join(", ")}`);
} }
// Output to console // Output to console
@@ -169,7 +175,6 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
...config.env, ...config.env,
}, },
}); });
console.log("yolo", process.env);
// Handle Claude process errors // Handle Claude process errors
claudeProcess.on("error", (error) => { claudeProcess.on("error", (error) => {
@@ -302,7 +307,10 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
await writeFile("output.txt", output); await writeFile("output.txt", output);
// Process output.txt into JSON and save to execution file // Process output.txt into JSON and save to execution file
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt"); // Increase maxBuffer from Node.js default of 1MB to 10MB to handle large Claude outputs
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt", {
maxBuffer: 10 * 1024 * 1024,
});
await writeFile(EXECUTION_FILE, jsonOutput); await writeFile(EXECUTION_FILE, jsonOutput);
console.log(`Log saved to ${EXECUTION_FILE}`); console.log(`Log saved to ${EXECUTION_FILE}`);
@@ -319,7 +327,10 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
if (output) { if (output) {
try { try {
await writeFile("output.txt", output); await writeFile("output.txt", output);
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt"); // Increase maxBuffer from Node.js default of 1MB to 10MB to handle large Claude outputs
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt", {
maxBuffer: 10 * 1024 * 1024,
});
await writeFile(EXECUTION_FILE, jsonOutput); await writeFile(EXECUTION_FILE, jsonOutput);
core.setOutput("execution_file", EXECUTION_FILE); core.setOutput("execution_file", EXECUTION_FILE);
} catch (e) { } catch (e) {

View File

@@ -0,0 +1,59 @@
import * as core from "@actions/core";
export function collectActionInputsPresence(): void {
const inputDefaults: Record<string, string> = {
trigger_phrase: "@claude",
assignee_trigger: "",
label_trigger: "claude",
base_branch: "",
branch_prefix: "claude/",
allowed_bots: "",
mode: "tag",
model: "",
anthropic_model: "",
fallback_model: "",
allowed_tools: "",
disallowed_tools: "",
custom_instructions: "",
direct_prompt: "",
override_prompt: "",
mcp_config: "",
additional_permissions: "",
claude_env: "",
settings: "",
anthropic_api_key: "",
claude_code_oauth_token: "",
github_token: "",
max_turns: "",
use_sticky_comment: "false",
use_commit_signing: "false",
experimental_allowed_domains: "",
};
const allInputsJson = process.env.ALL_INPUTS;
if (!allInputsJson) {
console.log("ALL_INPUTS environment variable not found");
core.setOutput("action_inputs_present", JSON.stringify({}));
return;
}
let allInputs: Record<string, string>;
try {
allInputs = JSON.parse(allInputsJson);
} catch (e) {
console.error("Failed to parse ALL_INPUTS JSON:", e);
core.setOutput("action_inputs_present", JSON.stringify({}));
return;
}
const presentInputs: Record<string, boolean> = {};
for (const [name, defaultValue] of Object.entries(inputDefaults)) {
const actualValue = allInputs[name] || "";
const isSet = actualValue !== defaultValue;
presentInputs[name] = isSet;
}
core.setOutput("action_inputs_present", JSON.stringify(presentInputs));
}

View File

@@ -13,9 +13,12 @@ import { parseGitHubContext, isEntityContext } from "../github/context";
import { getMode, isValidMode, DEFAULT_MODE } from "../modes/registry"; import { getMode, isValidMode, DEFAULT_MODE } from "../modes/registry";
import type { ModeName } from "../modes/types"; import type { ModeName } from "../modes/types";
import { prepare } from "../prepare"; import { prepare } from "../prepare";
import { collectActionInputsPresence } from "./collect-inputs";
async function run() { async function run() {
try { try {
collectActionInputsPresence();
// Step 1: Get mode first to determine authentication method // Step 1: Get mode first to determine authentication method
const modeInput = process.env.MODE || DEFAULT_MODE; const modeInput = process.env.MODE || DEFAULT_MODE;

View File

@@ -3,8 +3,9 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod"; import { z } from "zod";
import { readFile } from "fs/promises"; import { readFile, stat } from "fs/promises";
import { join } from "path"; import { join } from "path";
import { constants } from "fs";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { GITHUB_API_URL } from "../github/api/config"; import { GITHUB_API_URL } from "../github/api/config";
import { retryWithBackoff } from "../utils/retry"; import { retryWithBackoff } from "../utils/retry";
@@ -162,6 +163,34 @@ async function getOrCreateBranchRef(
return baseSha; return baseSha;
} }
// Get the appropriate Git file mode for a file
async function getFileMode(filePath: string): Promise<string> {
try {
const fileStat = await stat(filePath);
if (fileStat.isFile()) {
// Check if execute bit is set for user
if (fileStat.mode & constants.S_IXUSR) {
return "100755"; // Executable file
} else {
return "100644"; // Regular file
}
} else if (fileStat.isDirectory()) {
return "040000"; // Directory (tree)
} else if (fileStat.isSymbolicLink()) {
return "120000"; // Symbolic link
} else {
// Fallback for unknown file types
return "100644";
}
} catch (error) {
// If we can't stat the file, default to regular file
console.warn(
`Could not determine file mode for ${filePath}, using default: ${error}`,
);
return "100644";
}
}
// Commit files tool // Commit files tool
server.tool( server.tool(
"commit_files", "commit_files",
@@ -223,6 +252,9 @@ server.tool(
? filePath ? filePath
: join(REPO_DIR, filePath); : join(REPO_DIR, filePath);
// Get the proper file mode based on file permissions
const fileMode = await getFileMode(fullPath);
// Check if file is binary (images, etc.) // Check if file is binary (images, etc.)
const isBinaryFile = const isBinaryFile =
/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test( /\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test(
@@ -261,7 +293,7 @@ server.tool(
// Return tree entry with blob SHA // Return tree entry with blob SHA
return { return {
path: filePath, path: filePath,
mode: "100644", mode: fileMode,
type: "blob", type: "blob",
sha: blobData.sha, sha: blobData.sha,
}; };
@@ -270,7 +302,7 @@ server.tool(
const content = await readFile(fullPath, "utf-8"); const content = await readFile(fullPath, "utf-8");
return { return {
path: filePath, path: filePath,
mode: "100644", mode: fileMode,
type: "blob", type: "blob",
content: content, content: content,
}; };