Compare commits

...

2 Commits

Author SHA1 Message Date
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
3 changed files with 37 additions and 5 deletions

View File

@@ -178,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.84 curl -fsSL https://claude.ai/install.sh | bash -s 1.0.85
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Setup Network Restrictions - name: Setup Network Restrictions

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.84 run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.85
- name: Run Claude Code Action - name: Run Claude Code Action
shell: bash shell: bash

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,
}; };