Feature request: expose file mode (executable bit) on push_files
Use case
Agentic coding harnesses use this MCP server to publish files into a repo
on behalf of an LLM (issue execution, autonomous PR workflows, etc.).
When the work the agent is asked to produce is a shell script, build
helper, or other executable, the resulting file needs the executable
bit set (100755) — otherwise reviewers either have to chmod +x after
merge or merge a broken artifact.
Today there is no way for an MCP client to land an executable file
through this server. Both file-write tools use APIs that surface a fixed
mode:
create_or_update_file → Contents API (PUT /repos/.../contents/{path}),
which always writes mode 100644. This is a GitHub API limitation, not
something this server can route around.
push_files → Git Data API (CreateTree + CreateCommit +
UpdateRef). The underlying API does support per-entry mode, but
the tool's input schema doesn't expose it. Each entry is currently
{content, path} only.
We've observed agents react to this in two unhelpful ways:
- Quiet hallucination. The agent reports in the PR body that it
pushed with mode 100755 when in fact the blob landed as 100644.
- Workaround scope drift. The agent invents a second file (e.g.
scripts/permissions.sh containing find scripts/ -exec chmod +x)
that wasn't part of the task, hoping a CI step or human will run it.
A first-class mode parameter on push_files would close both failure
modes cleanly.
Proposed change
Extend the push_files.files[] input schema with an optional mode
field, validated against the set the Git Data API accepts:
| Mode |
Meaning |
100644 |
regular file (default if omitted) |
100755 |
executable file |
120000 |
symlink |
040000 |
subdirectory (subtree) |
160000 |
submodule |
For most agentic use cases only 100644 (default) and 100755 matter,
but echoing the full Git Data API set keeps parity with the underlying
endpoint.
Backwards compatible: existing callers that omit mode get 100644,
which is what they get today.
Implementation pointer
The relevant code is already calling Git.CreateTree with a
[]*github.TreeEntry (in pkg/github/repositories.go, around the
PushFiles tool definition). The go-github TreeEntry struct
already has a Mode *string field — it just isn't being set from the
tool input. Threading it through should be a small change:
// in the loop that builds entries:
mode := "100644"
if f.Mode != nil {
mode = *f.Mode
}
entries = append(entries, &github.TreeEntry{
Path: github.String(f.Path),
Mode: github.String(mode),
Type: github.String("blob"),
Content: github.String(f.Content),
})
Plus input-schema additions for the optional field with the validated
set above.
Single-file case
create_or_update_file can't be fixed the same way because the Contents
API doesn't support mode at all — it's a GitHub API constraint, not a
server constraint. For the single-file executable case, callers can use
push_files with a one-entry files array. Worth noting that in the
tool's description so clients pick the right call.
Out of scope
- Changes to
create_or_update_file (blocked by Contents API).
- Symlink/submodule write flows beyond just accepting their modes on the
schema.
- Validating the executable-bit semantics on the receiving end.
Context
Filed against this server because we've been hitting the limitation
from a Claude-Code-plans / Goose-executes harness
(why-pengo/claude_and_goose),
where local-model executors are asked to ship small bash helpers as
part of issue-driven PRs. Happy to send a PR if the proposed shape
looks right.
Feature request: expose file mode (executable bit) on
push_filesUse case
Agentic coding harnesses use this MCP server to publish files into a repo
on behalf of an LLM (issue execution, autonomous PR workflows, etc.).
When the work the agent is asked to produce is a shell script, build
helper, or other executable, the resulting file needs the executable
bit set (
100755) — otherwise reviewers either have tochmod +xaftermerge or merge a broken artifact.
Today there is no way for an MCP client to land an executable file
through this server. Both file-write tools use APIs that surface a fixed
mode:
create_or_update_file→ Contents API (PUT /repos/.../contents/{path}),which always writes mode
100644. This is a GitHub API limitation, notsomething this server can route around.
push_files→ Git Data API (CreateTree+CreateCommit+UpdateRef). The underlying API does support per-entry mode, butthe tool's input schema doesn't expose it. Each entry is currently
{content, path}only.We've observed agents react to this in two unhelpful ways:
pushed with mode
100755when in fact the blob landed as100644.scripts/permissions.shcontainingfind scripts/ -exec chmod +x)that wasn't part of the task, hoping a CI step or human will run it.
A first-class
modeparameter onpush_fileswould close both failuremodes cleanly.
Proposed change
Extend the
push_files.files[]input schema with an optionalmodefield, validated against the set the Git Data API accepts:
100644100755120000040000160000For most agentic use cases only
100644(default) and100755matter,but echoing the full Git Data API set keeps parity with the underlying
endpoint.
Backwards compatible: existing callers that omit
modeget100644,which is what they get today.
Implementation pointer
The relevant code is already calling
Git.CreateTreewith a[]*github.TreeEntry(inpkg/github/repositories.go, around thePushFilestool definition). The go-githubTreeEntrystructalready has a
Mode *stringfield — it just isn't being set from thetool input. Threading it through should be a small change:
Plus input-schema additions for the optional field with the validated
set above.
Single-file case
create_or_update_filecan't be fixed the same way because the ContentsAPI doesn't support mode at all — it's a GitHub API constraint, not a
server constraint. For the single-file executable case, callers can use
push_fileswith a one-entryfilesarray. Worth noting that in thetool's description so clients pick the right call.
Out of scope
create_or_update_file(blocked by Contents API).schema.
Context
Filed against this server because we've been hitting the limitation
from a Claude-Code-plans / Goose-executes harness
(why-pengo/claude_and_goose),
where local-model executors are asked to ship small bash helpers as
part of issue-driven PRs. Happy to send a PR if the proposed shape
looks right.