code_search's underlying gitea.Client.SearchCode called GET /api/v1/repos/{owner}/{repo}/search?type=code — an endpoint that does not exist on this Gitea instance. Confirmed against the live swagger spec (https://git.d-ma.be/swagger.v1.json): only /repos/search, /repos/issues/search, /topics/search etc are real paths. Every call to code_search returned a raw 404 page not found.
Gitea's own web UI code search (/{owner}/{repo}/search?q=...&type=code) works, but falls back to server-side git grep because no Repository Indexer is enabled here — and that fallback is an HTML-only route, never a JSON API. So no REST endpoint ever existed to call correctly; the original implementation was built against a path that was never real.
Why it went undetected: the existing test suite mocked the fantasy endpoint directly — asserting the request hit .../search?type=code and handing back a canned JSON envelope. Tests passed; the tool never worked against the real server.
Replaced the REST call with a client-side implementation using primitives already proven against the live server this session (GetRepo, GetTree, GetFileContents): resolve default branch, walk the tree, substring-match q (case-insensitive, literal) against fetched file contents. Bounded by a 2000-file scan cap, a 512KB file-size cap (checked via the tree listing before any fetch), a binary-extension denylist, and a null-byte content check for anything the denylist misses.
Verified two ways:
Full rewritten test suite (client + tool layer) against fakes serving the REAL endpoints, not the fantasy one.
Manually replicated the algorithm against production data (mathias/gitea-mcp itself): GetRepo → resolved main; GetTree → 157 real blobs; GetFileContents on a known file → real content, occurrence count matched exactly what the new scoring logic would compute. This is the check that matters most given the root cause was tests validating a mock instead of reality — didn't want to repeat that mistake verifying the fix.
Tool layer (internal/tools/code_search.go) unchanged — SearchCode's signature and []CodeSearchHit contract are identical, so the fix is isolated to the client layer.
Known limitation (by design, not a bug)
Pagination re-scans the full tree on every call (no server-side index to page through incrementally) — acceptable for the repo sizes this targets. A real indexer (bleve/elasticsearch) enabled server-side would be the long-term fix; that's an infra decision, not something gitea-mcp controls.
## Problem
`code_search`'s underlying `gitea.Client.SearchCode` called `GET /api/v1/repos/{owner}/{repo}/search?type=code` — an endpoint that does not exist on this Gitea instance. Confirmed against the live swagger spec (`https://git.d-ma.be/swagger.v1.json`): only `/repos/search`, `/repos/issues/search`, `/topics/search` etc are real paths. Every call to `code_search` returned a raw `404 page not found`.
Gitea's own web UI code search (`/{owner}/{repo}/search?q=...&type=code`) works, but falls back to server-side `git grep` because no Repository Indexer is enabled here — and that fallback is an HTML-only route, never a JSON API. So no REST endpoint ever existed to call correctly; the original implementation was built against a path that was never real.
**Why it went undetected:** the existing test suite mocked the fantasy endpoint directly — asserting the request hit `.../search?type=code` and handing back a canned JSON envelope. Tests passed; the tool never worked against the real server.
## Fix (v0.11.0, commit a16c5b0)
Replaced the REST call with a client-side implementation using primitives already proven against the live server this session (`GetRepo`, `GetTree`, `GetFileContents`): resolve default branch, walk the tree, substring-match `q` (case-insensitive, literal) against fetched file contents. Bounded by a 2000-file scan cap, a 512KB file-size cap (checked via the tree listing before any fetch), a binary-extension denylist, and a null-byte content check for anything the denylist misses.
Verified two ways:
1. Full rewritten test suite (client + tool layer) against fakes serving the REAL endpoints, not the fantasy one.
2. **Manually replicated the algorithm against production data** (`mathias/gitea-mcp` itself): `GetRepo` → resolved `main`; `GetTree` → 157 real blobs; `GetFileContents` on a known file → real content, occurrence count matched exactly what the new scoring logic would compute. This is the check that matters most given the root cause was tests validating a mock instead of reality — didn't want to repeat that mistake verifying the fix.
Tool layer (`internal/tools/code_search.go`) unchanged — `SearchCode`'s signature and `[]CodeSearchHit` contract are identical, so the fix is isolated to the client layer.
## Known limitation (by design, not a bug)
Pagination re-scans the full tree on every call (no server-side index to page through incrementally) — acceptable for the repo sizes this targets. A real indexer (bleve/elasticsearch) enabled server-side would be the long-term fix; that's an infra decision, not something gitea-mcp controls.
`task check` green (exit 0, 0 FAIL, 0 lint issues) throughout.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
code_search's underlyinggitea.Client.SearchCodecalledGET /api/v1/repos/{owner}/{repo}/search?type=code— an endpoint that does not exist on this Gitea instance. Confirmed against the live swagger spec (https://git.d-ma.be/swagger.v1.json): only/repos/search,/repos/issues/search,/topics/searchetc are real paths. Every call tocode_searchreturned a raw404 page not found.Gitea's own web UI code search (
/{owner}/{repo}/search?q=...&type=code) works, but falls back to server-sidegit grepbecause no Repository Indexer is enabled here — and that fallback is an HTML-only route, never a JSON API. So no REST endpoint ever existed to call correctly; the original implementation was built against a path that was never real.Why it went undetected: the existing test suite mocked the fantasy endpoint directly — asserting the request hit
.../search?type=codeand handing back a canned JSON envelope. Tests passed; the tool never worked against the real server.Fix (v0.11.0, commit
a16c5b0)Replaced the REST call with a client-side implementation using primitives already proven against the live server this session (
GetRepo,GetTree,GetFileContents): resolve default branch, walk the tree, substring-matchq(case-insensitive, literal) against fetched file contents. Bounded by a 2000-file scan cap, a 512KB file-size cap (checked via the tree listing before any fetch), a binary-extension denylist, and a null-byte content check for anything the denylist misses.Verified two ways:
mathias/gitea-mcpitself):GetRepo→ resolvedmain;GetTree→ 157 real blobs;GetFileContentson a known file → real content, occurrence count matched exactly what the new scoring logic would compute. This is the check that matters most given the root cause was tests validating a mock instead of reality — didn't want to repeat that mistake verifying the fix.Tool layer (
internal/tools/code_search.go) unchanged —SearchCode's signature and[]CodeSearchHitcontract are identical, so the fix is isolated to the client layer.Known limitation (by design, not a bug)
Pagination re-scans the full tree on every call (no server-side index to page through incrementally) — acceptable for the repo sizes this targets. A real indexer (bleve/elasticsearch) enabled server-side would be the long-term fix; that's an infra decision, not something gitea-mcp controls.
task checkgreen (exit 0, 0 FAIL, 0 lint issues) throughout.