Repository navigation
feat(git): browse a connection's repositories when linking - #691
Conversation
Add GET /projects/{id}/git/connections/{identityId}/repositories, which
pages ListRepositories of the provider for a connection the project can
use, and a searchable picker in the link form. Typing a path or URL stays
possible; a provider that cannot list falls back to it.
Refs Paca-AI#656
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… gate q is limited to 200 characters and cursor to 1024; longer values get a 400. A route coverage test pins git:write so git:read alone gets a 403. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web input caps the search at 200 characters, so a multibyte search under that cap was refused with 400. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ Minor suggestions only — two small robustness points inline.
Reviewed changes
A repository picker for the Git link form, backed by a new endpoint that calls the provider SPI's ListRepositories.
- API —
GET /projects/{id}/git/connections/{identityId}/repositories?q=&cursor=, pinned togit:write. The service confines the connection withvisibleIdentity(workspace-shared or the project's own), maps a providerunsupportedanswer to a quiet200 {supported:false}, and routes other provider errors throughfailWithProviderMessage. The DTO returns only public fields, so noprovider_ref/clone URL leaks. - Web —
GitRepositoryPicker(debounced search,Load more, linked repos disabled, private marked, unsupported/error fallback) wired into the link form; picking only fills the field, so typed path/URL linking is unchanged. - Tests & docs — Go service/handler/route-coverage tests, the vitest picker suite, strings in all nine locales, and SPI/workflow docs.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| // Upper bounds on the picker's inputs, which are forwarded to the platform. | ||
| const ( | ||
| maxRepositorySearchLen = 200 | ||
| maxRepositoryCursorLen = 1024 |
There was a problem hiding this comment.
The cursor is opaque and provider-issued, and the web forwards next_cursor verbatim on Load more — so any provider whose cursor exceeds 1024 bytes gets a hard 400 on page 2, without the graceful fallback the rest of this endpoint has. Since the bound is enforced on data core itself receives from the plugin, consider lifting it, or documenting it in docs/plugins/git-provider-spi.md (which this PR already touches) so provider authors know to keep cursors short.
Technical details
# Cursor length cap vs. provider-issued cursors
## Affected sites
- services/api/internal/transport/http/handler/git_handler.go:482 — `maxRepositoryCursorLen = 1024`, applied at :501
- apps/web/src/components/projects/settings/GitRepositoryPicker.tsx:55 — `getNextPageParam` forwards the provider's `next_cursor`
## Required outcome
- A provider that returns a `NextCursor` longer than the cap must not make **Load more** fail; the cap should be documented as a provider contract, raised, or removed.| <Skeleton className="h-8 w-full" /> | ||
| </div> | ||
| ); | ||
| } else if (list.isError) { |
There was a problem hiding this comment.
In React Query v5 a failed fetchNextPage sets status to error while keeping the existing data, so list.isError is true here and this branch replaces the already-loaded repositories with "Couldn't load the list" — even though the list did load and only one more page failed. Consider gating the full-replace on !list.data and surfacing a next-page failure near Load more instead, so a transient error (or an over-long cursor, above) does not wipe the visible list.
pikann
left a comment
There was a problem hiding this comment.
LGTM! Thank you for the contribution! 🚀

Refs #656 (item 3, "display the repository list picker")
What
Linking a repository meant typing
group/subgroup/projectby hand. The provider SPI already hasListRepositories, but core never called it. This wires it up.GET /projects/{id}/git/connections/{identityId}/repositories?q=&cursor=, gated bygit:write(it uses the connection's credential, like linking). The connection must be one the project can use (the workspace's or its own); anything else isGIT_NOT_FOUND. Returns{items, next_cursor, supported}.unsupportedgives200withsupported: falseand no items, so the form falls back to the text field without an error.failWithProviderMessage(fix(git): surface the provider's message when linking a repository or registering a webhook fails #660), so the platform's reason shows under the list.git-provider-spi.md,git-agent-workflow.md).No SPI change, no migration, MCP unchanged.
Tests
provider_message, 400 onqover 200 orcursorover 1024 chars).git:writeon the picker (git:readalone gets 403).go vet,go test ./..., golangci-lint (0 issues),tsc -b, biome, i18n parity.com.paca.gitlab0.6.0 wasm against GitLab CE 19.5.1 (this branch merged with the private hosts one, Chrome headless): list, search, link, Load more over 35 projects, and a revoked token showing the provider's message with the text field still usable. A 403 on the listing itself was not exercised.🤖 Generated with Claude Code