Skip to content

create_or_update_file content description doesn't specify plaintext, leading models to double-encode #2981

Description

@ericsciple

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

  1. Point an agent at a repository with create_or_update_file available.
  2. Ask it to modify an existing text file and commit the result.
  3. 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_filesfiles[].content, described as "file content"
  • create_gist"Content for simple single-file gist creation"
  • update_gist"Content for the file"

Possibly related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions