Describe the bug
The content parameter of create_or_update_file expects plaintext, but its description
does not say so, and the REST endpoint the tool wraps expects base64. Models frequently
resolve that ambiguity by base64-encoding the content themselves, which the server then encodes
again. The result is a successful API call that commits base64 text as the file's contents, with
no error anywhere.
The schema says only:
"content": { "description": "Content of the file", "type": "string" }
The server treats it as plaintext and encodes exactly once
(pkg/github/repositories.go#L484-L485):
// json.Marshal encodes byte arrays with base64, which is required for the API.
contentBytes := []byte(content)
That is correct behavior. The problem is that a model reading the tool description has no way to
know it, while it has very likely seen
PUT /repos/{owner}/{repo}/contents/{path},
whose content field is documented as "The new file content, using Base64 encoding." Given a
parameter named content on a tool named create_or_update_file, pre-encoding is a reasonable
inference.
The failure is silent in both directions: the tool returns success, and the resulting file looks
fine to the API. It surfaces only when a human opens the file and finds one long base64 line.
Decoding it yields exactly the content the model intended to write.
Affected versions
Not version-specific. The description is byte-identical in v1.6.0, v1.8.0, and main, so
upgrading does not change it. The remote server runs the same code and is equally affected.
Steps to reproduce
- Point an agent at a repository with
create_or_update_file available.
- Ask it to modify an existing text file and commit the result.
- Inspect the committed file.
Whether the model pre-encodes is model- and prompt-dependent, so this does not reproduce 100% of
the time. That is arguably what makes it worth fixing: it is an intermittent, silent corruption
that depends on how a given model resolves an ambiguous parameter description.
Expected behavior
The description states the expected encoding, so a model does not have to guess.
Suggested fix
A description change is probably sufficient:
"content": {
"description": "Content of the file as plain text. Do NOT base64-encode it; the server handles encoding.",
"type": "string"
}
Optionally, defensively reject input that is unambiguously already-encoded (valid base64 whose
decoding yields valid UTF-8 that differs from the input). That has false-positive risk for files
that genuinely contain base64, so the description fix is the safer first step.
The same ambiguity exists on sibling tools and would be worth fixing together:
push_files — files[].content, described as "file content"
create_gist — "Content for simple single-file gist creation"
update_gist — "Content for the file"
Possibly related
Describe the bug
The
contentparameter ofcreate_or_update_fileexpects plaintext, but its descriptiondoes not say so, and the REST endpoint the tool wraps expects base64. Models frequently
resolve that ambiguity by base64-encoding the content themselves, which the server then encodes
again. The result is a successful API call that commits base64 text as the file's contents, with
no error anywhere.
The schema says only:
The server treats it as plaintext and encodes exactly once
(
pkg/github/repositories.go#L484-L485):That is correct behavior. The problem is that a model reading the tool description has no way to
know it, while it has very likely seen
PUT /repos/{owner}/{repo}/contents/{path},whose
contentfield is documented as "The new file content, using Base64 encoding." Given aparameter named
contenton a tool namedcreate_or_update_file, pre-encoding is a reasonableinference.
The failure is silent in both directions: the tool returns success, and the resulting file looks
fine to the API. It surfaces only when a human opens the file and finds one long base64 line.
Decoding it yields exactly the content the model intended to write.
Affected versions
Not version-specific. The description is byte-identical in
v1.6.0,v1.8.0, andmain, soupgrading does not change it. The remote server runs the same code and is equally affected.
Steps to reproduce
create_or_update_fileavailable.Whether the model pre-encodes is model- and prompt-dependent, so this does not reproduce 100% of
the time. That is arguably what makes it worth fixing: it is an intermittent, silent corruption
that depends on how a given model resolves an ambiguous parameter description.
Expected behavior
The description states the expected encoding, so a model does not have to guess.
Suggested fix
A description change is probably sufficient:
Optionally, defensively reject input that is unambiguously already-encoded (valid base64 whose
decoding yields valid UTF-8 that differs from the input). That has false-positive risk for files
that genuinely contain base64, so the description fix is the safer first step.
The same ambiguity exists on sibling tools and would be worth fixing together:
push_files—files[].content, described as"file content"create_gist—"Content for simple single-file gist creation"update_gist—"Content for the file"Possibly related
create_or_update_file silently truncates large files) was closed as not reproducible.Pre-encoding would inflate content by roughly 33% and collapse it to a single line, which could
present as truncation, so these may share a root cause.