--- source_keys: - gitea-mcp-repo - gitea-mcp-slim-go --- # Creating, updating and deleting files `sha` is the optimistic-concurrency token for every write, and it comes from `get_file_contents`'s **top-level** `sha` field — never from `content.sha`, a directory listing, or a tree entry. Do not guess or reuse a stale value: a mismatched SHA is rejected exactly like a missing one. `content` is base64-encoded, and the branch the commit lands on is `branch_name`, not `ref`. ## Create a new file Call `create_or_update_file(owner, repo, path, content, message, branch_name)` with `sha` omitted entirely. An omitted `sha` always means *create*, so the call returns HTTP 409 if the path already exists. To branch off as part of the same write, pass `new_branch_name`: the branch is created and the commit lands on it in one call, replacing a separate branch-creation step. ## Update an existing file 1. `get_file_contents(owner, repo, ref: , path)` → read the top-level `sha`. 2. `create_or_update_file(owner, repo, path, content, message, branch_name, sha: )`. ## Delete a file Same SHA-first pattern, with no create-style fallback. `sha` is schema-**required** on `delete_file`, unlike `create_or_update_file` where omitting it means *create* — so an omitted `sha` is rejected client-side by input validation and the call never reaches Gitea. The HTTP 422 that is actually reachable here is the stale-`sha` case. 1. `get_file_contents(owner, repo, ref: , path)` → read the top-level `sha`. 2. `delete_file(owner, repo, path, message, branch_name, sha: )`. ## Worked sequence — new file on a new branch, then a PR ```text 1. create_or_update_file owner, repo path: "docs/example.md" content: "" message: "docs: add example" branch_name: "main" new_branch_name: "feat/add-example" ← branches off before the commit lands (sha omitted — this is a new file) 2. Hand off to gitea-prs to open a PR from "feat/add-example" into "main". ``` Step 1 needs no preceding read: a brand-new path has no SHA. Fetch the current file first only when the write replaces an existing one. ## Triaging a failed write | Symptom | Cause | Action | |---|---|---| | HTTP 409 | `sha` omitted on a path that already exists | Fetch the current SHA, retry as an update | | HTTP 422 | Stale `sha` — the file changed between the read and the write | Re-fetch the SHA immediately before the write | | 403 or 422 with no SHA explanation | Branch protection requires signed commits | Stop and report | | HTTP 413 | Reverse-proxy body limit in front of Gitea | Report; retrying cannot fix it | | Client-side input-validation error naming `sha` | `sha` omitted on `delete_file`, where it is schema-required | Fetch the current SHA and retry — nothing was sent to Gitea | | HTTP 404 | Wrong path, or a token without `write:repository` | Verify the path, then the token's scopes | **Signed commits.** These writes create commits server-side from a bare API token with no 2FA or PGP context. If the target branch's protection rule requires signed commits, Gitea rejects the write as a generic 403 or 422 that never names signing, and reads against that same branch keep succeeding right up until the write. When a write fails without a clean 409 or 404 explanation, check the branch's protection rule before assuming the SHA is wrong and retrying. **Payload size.** base64 inflates `content` roughly 33% over the raw file size, and a 413 is usually a reverse-proxy body-size limit in front of the Gitea instance rather than a Gitea-side rejection. No amount of retrying, or changing the SHA, path, or branch, will fix it — the proxy's config has to be raised, which is outside this skill's control. Surface that distinction instead of retrying the same call.