github / github/github-mcp-server
Add file mode (executable bit) support to `push_files`
- Vorherrschende Sprache
- Go
- Sterne
- 33k
- Forks
- 5k
- Ø Merge
- 2 T. 1 Std.
- Gemergte PRs (30 T.)
- 52
Beschreibung
# 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:
1. **Quiet hallucination.** The agent reports in the PR body that it
pushed with mode `100755` when in fact the blob landed as `100644`.
2. **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:
```go
// 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](https://github.com/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.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.