v0.11.0
code_search was calling GET /api/v1/repos/{owner}/{repo}/search?type=code — an
endpoint that does not exist. Confirmed against a live Gitea 1.25.5 instance's
swagger spec: only /repos/search, /repos/issues/search, /topics/search etc are
real. Gitea's own web UI code search falls back to server-side `git grep`
because no Repository Indexer is enabled here, and that fallback is an
HTML-only route, not JSON API — so no REST endpoint exists to call, working or
not. Every call to code_search 404'd on the real server.
This went undetected because the existing tests mocked the fantasy endpoint
directly (asserting the request path was .../search?type=code and handing back
a canned JSON envelope) — a textbook case of tests validating a fake instead of
the real system, giving false confidence the tool worked.
SearchCode now does the search itself: resolves the default branch, walks the
tree (GetTree), and substring-matches q (case-insensitive, literal — not a
regex, to keep behavior predictable and avoid a ReDoS surface from
user-supplied input) against fetched file contents (GetFileContents) — the same
approach Gitea's own indexer-less fallback uses, just client-side. Bounded by:
- codeSearchMaxFiles (2000) — files scanned per call
- codeSearchMaxFileSize (512KB, checked via the tree listing's own Size field,
before any fetch) — skip large blobs
- a binary-extension denylist checked before fetch, plus a null-byte content
check after fetch, for extensions the denylist misses
Pagination is over the full sorted result set, recomputed each call (no
server-side index to page through incrementally) — acceptable for the repo
sizes this targets; documented as a known limitation, not a hidden footgun.
The tool layer (internal/tools/code_search.go) is UNCHANGED — SearchCode's
signature and the []CodeSearchHit contract are identical, so this is fully
isolated to the client layer + its tests.
Tests: match found in tree, case-insensitive matching, binary extension
skipped WITHOUT fetching (asserted via the fake's fetch log, not just absent
from results), oversized file skipped without fetching, null-byte content
skipped after fetching, pagination across a full sorted set, empty-query
validation — plus the tool-layer single-repo and fan-out tests rewritten
against the same real endpoints (GetRepo/GetTree/GetFileContents +
ListRepos), replacing their fantasy-endpoint fakes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gitea-mcp
Streamable HTTP MCP service exposing Gitea repo operations to Claude apps.
See ~/dev/AI/infra/docs/superpowers/specs/2026-05-04-gitea-mcp-gitops-workflow-design.md.
Quickstart
task setup:hooks # installs .githooks/pre-push — runs task check before every push
task check # context sync + lint + test + vet
task build # produces bin/gitea-mcp
This repo uses Trunk-Based Development. Commit directly to main. The pre-push
hook enforces the quality gate locally; CI re-runs task check on every push.
Languages
Go
98.3%
Shell
1.5%
Dockerfile
0.2%