Uh oh!
There was an error while loading. Please reload this page.
feat: add Gitea source-control provider - #8232
Conversation
Adds `gitea` to `SourceControlProviderKind` and gives it provider-neutral presentation metadata (PR / pull request terminology, `tea pulls checkout` example). Static remote detection matches only obvious installations — `gitea.com` and hosts carrying a `gitea` DNS label — because Gitea is usually self-hosted on a hostname that says nothing about it. Arbitrary hosts stay `unknown` here and are refined later from `tea`'s authenticated logins. No provider is registered yet, so behavior is unchanged for every existing host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`tea logins list --output json` is how the server learns which Gitea instances it can talk to. This parses that output into a typed list and adds host lookup used both by the Source Control settings card and by unknown-remote refinement. Two details verified against tea 0.15.1 rather than assumed: - `default` is reported as the string "true"/"false", not a boolean. - No token appears in the output, so it is safe to parse and log around. Host matching compares hostnames with ports stripped, because a Gitea instance is routinely reached over HTTPS on one port and SSH on another; an SSH remote would otherwise never match its own login. Matching stays exact per DNS label, so a suffix like evil-git.example.com cannot impersonate git.example.com. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wraps the official Gitea CLI for the operations SourceControlProvider needs,
going through `tea api` rather than `tea pulls`. Two verified gaps forced that
choice: `tea pulls list` cannot filter by head branch, and its --state accepts
only all/open/closed, while T3 needs head filtering and a distinct merged state.
`tea api` is the same escape hatch GitLabCli uses with `glab api`.
The important detail is error handling. `tea api` exits 0 even for HTTP 401,
403, 404 and 429, so exit-code classification silently reports failures as
success — a 404 would look like "no pull request exists" and T3 would open a
duplicate. Every call therefore passes -i, which puts the status line on stderr
and leaves clean JSON on stdout, and failures are classified from that status.
Gitea has no head filter on its list endpoint, so pages are walked and matched
locally, bounded to 5 pages of 50 and exiting early. The common case is one
request. Merged is read off the `merged` flag, since Gitea models a merged PR as
closed. PR bodies are passed as `-F body=@file`, which was verified to encode
file contents as a JSON string even when they begin with `{`, keeping bodies out
of argv.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Adds GiteaSourceControlProvider and wires it into the registry, so a Gitea remote now resolves to a real provider instead of the `unknown` stub whose every method fails. Detection works in two stages. Obvious hosts are matched statically, and any other host stays `unknown` until discovery asks `tea logins list` whether it is an instance the server is authenticated against. That keeps arbitrary Git hosts untouched and avoids probing unknown remotes over the network. Host comparison ignores ports, since a Gitea instance is commonly reached over HTTPS and SSH on different ones. The settings card reports the default `tea` login; additional instances are named in the detail rather than dropped, since the discovery contract holds a single account but refinement still consults every login. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drives the whole stacked action through the real Gitea provider, which is the workflow the original report failed on: a Gitea remote resolved to `unknown`, whose stub failed every call, so the action died with "No unknown source control provider is registered." Also covers the duplicate case. GitManager looks for an existing PR before creating one, and swallowing a provider error there would open a second PR, so the test asserts create is never called when one is already open. makeManager gained an optional sourceControlProvider override; it defaults to the GitHub provider, so every existing test is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds Gitea to the Add Project source list and the publish provider options, and documents setup in the user guide. The mobile and web clients both draw Gitea with the neutral pull-request mark: no Gitea logo is bundled here yet, and borrowing another host's brand would be wrong. A real icon can drop in later without touching this wiring. One bounded limitation is documented rather than designed around: a short owner/repository path resolves against tea's default login, so cloning from a second Gitea instance needs a full Git URL. Representing per-instance selection would mean a new account-selection contract, which this change does not add. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ish the UI surfaces
Review pass over the Gitea provider. The important fix is repository creation.
Gitea splits creation in two: POST /user/repos creates under the authenticated
user, while POST /orgs/{org}/repos requires a real organization and 404s for a
plain user. Every owner was being sent to the orgs endpoint. Because the publish
dialog prefills the signed-in account as the owner and refuses to submit without
one, the ordinary input is `<you>/name` — so publishing to Gitea would have
failed for essentially every user. Creation now resolves the authenticated
account first and picks the endpoint accordingly.
The rest is reach. Gitea was in the Add Project list but missing from four
surfaces that each keep their own provider enumeration:
- The publish dialog had a separate PublishProviderKind that never included it,
so the previous commit's message overstated what worked. Its host label reads
the authenticated instance from discovery, since a self-hosted Gitea has no
canonical host to hardcode.
- CommandPalette kept private copies of the Add Project types, which is why it
drifted. It now imports the canonical ones, which turned the gap into three
compiler errors and one silent one: an icon switch returning ReactNode, whose
missing arm type-checks fine and renders nothing.
- Two mobile guards narrowed by string equality and dropped gitea, sending the
Gitea entry to the URL flow.
- The settings icon map and the PR link context menu are Partial records, so
neither complained about the missing key.
Gitea PR URLs (/{owner}/{repo}/pulls/{n}) are now recognized by the in-app link
handler. The plural path is Gitea's own; the GitHub-ish hosts return earlier, so
the singular /pull/ shape is untouched.
`tea` reports an unconfigured instance as "no available login" and exits 1, which
classified as a generic command failure. It is now an authentication failure, so
the user is told to run `tea login add`.
Deliberately not changed: PROVIDER_REQUIREMENT in the pullRequest contract. Gitea
is not registered in the PR dashboard registry, so its reason is
provider-unsupported, which returns null before that table is consulted. An entry
would be dead code implying dashboard support that does not exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Reviewed the new Gitea service modules against the Effect service conventions. Structure, namespace imports, Context.Service + inline interface, make/layer, and environment-based dependency acquisition all look right, and the test-only service-instance injection in GitManager.test.ts is a legitimate seam. Three findings on error modeling in apps/server/src/sourceControl/GiteaCli.ts.
Posted via Macroscope — Effect Service Conventions
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
UI Consistency: 2 findings
Provider plumbing for the web client is otherwise complete and consistent (icon maps, labels, path hints, readiness, publish host, "Open on Gitea"). Two web surfaces that key off provider identity were not extended with the new kind, so Gitea users get different behaviour from every other host:
apps/web/src/lib/openPullRequestLink.ts(inline comment) — Gitea PR URLs are now readable for opening links, butapps/web/src/pullRequestReference.tsstill rejects them for input, while the input placeholder promises "PR URL, checkout command, or #42".apps/web/src/components/pullRequest/pullRequestDetail.logic.ts:855—TOOL_NOISEreplaces content-free host errors with an actionable hint forgithub|gitlab|bitbucket|azure devops, but the new provider'sGiteaCliCommandError.detailis"Gitea CLI command failed."(apps/server/src/sourceControl/GiteaCli.ts:121), which no pattern matches. A failed Gitea action therefore surfaces the bare "Gitea CLI command failed." string where the other hosts surface the hint. Smallest fix is to includegiteain the alternation:
- /^(github|gitlab|bitbucket|azure devops)?\s*(cli|api)?\s*(command\s*)?failed\.?$/iu,+ /^(github|gitlab|bitbucket|azure devops|gitea)?\s*(cli|api)?\s*(command\s*)?failed\.?$/iu,Minor, optional: the "Add project" command item's searchTerms (apps/web/src/components/CommandPalette.tsx:1547) lists github/gitlab/bitbucket/azure/devops but not gitea, so typing "gitea" no longer reaches the flow that now offers a Gitea source.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces a large, cross-cutting Gitea integration with new authenticated repository, pull-request, checkout, and publishing workflows across production server and client paths. A remaining error-classification concern may cause missing pull requests to surface as generic command failures, so the runtime behavior warrants human review. You can add or adjust custom eligibility rules. Learn more. |
MDragonryu
commented
Aug 25, 2026
Addressed the review findings in :\n\n- tightened Gitea execution error structure and HTTP status handling\n- classified only missing NAME: USAGE: VERSION: DESCRIPTION: tea tries to make use of context provided by the repository in $PWD if available. COMMANDS: ENTITIES: HELPERS: MISCELLANEOUS: SETUP: GLOBAL OPTIONS: USAGE: VERSION: DESCRIPTION: tea tries to make use of context provided by the repository in $PWD if available. COMMANDS: ENTITIES: HELPERS: MISCELLANEOUS: SETUP: GLOBAL OPTIONS: |
MDragonryu
commented
Aug 25, 2026
Addressed the review findings in commit 3e5118d:\n\n- tightened Gitea execution error structure and HTTP status handling\n- classified only missing tea executables as unavailable\n- handled malformed PR URLs and preserved raw pagination counts\n- matched fork source owners, honored checkout --force, and required a real tea login user\n- added Gitea pull-request URL/CLI reference recognition and UI noise/search coverage\n- preserved public GitHub/Bitbucket URL behavior while allowing self-hosted lookalike hosts to fall through to Gitea parsing\n\nVerification in the Linux devcontainer: 8 focused test files, 161 tests passed; server and web TypeScript checks completed with only existing repository suggestions. |
There was a problem hiding this comment.
Reviewed the new Gitea service modules against the Effect service conventions. Service definition, dependency acquisition (GiteaCli/GiteaSourceControlProvider both acquire their deps with yield* and expose make/layer), namespace imports, and the registry/layer wiring all look consistent with the sibling providers. Two error-modelling points below.
Posted via Macroscope — Effect Service Conventions
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
MDragonryu
commented
Aug 25, 2026
Follow-up fixes pushed in commit 3f98c41:\n\n- createPullRequest no longer supplies a PR reference to HTTP error mapping, so a create-time 404 is a GiteaCliCommandError; existing get/checkout PR 404s remain not-found\n- listPullRequests now requests sort=recentupdate, keeping current-branch PR discovery within the bounded page window\n\nThe focused Linux devcontainer suite now passes 8 files and 164 tests. |
Uh oh!
There was an error while loading. Please reload this page.
MDragonryu
commented
Aug 25, 2026
Follow-up Effect cleanup pushed in commit c0599af:\n\n- removed the redundant single-valued list decode operation field\n- removed the manufactured non-array JSON Error; decoder failures now carry the real schema failure while malformed JSON retains the real parse exception\n\nThe focused Linux devcontainer suite remains green: 8 files, 164 tests. |
There was a problem hiding this comment.
One finding: the new tea branch in classifyNonZeroExit changes backend error classification but has no focused test, and no tea rule produces not-found, so GiteaCli.checkoutPullRequest's not-found mapping is unreachable in production. Everything else in the new Gitea service modules follows the conventions (subpath namespace imports, inline Context.Service interface, make acquiring VcsProcess/GiteaCli from the environment, Schema.TaggedErrorClass errors with structural attributes and preserved cause, exported Schema.is predicate, layer at the bottom).
Posted via Macroscope — Effect Service Conventions
| // `tea` reports an unconfigured or unmatched instance this way, and it is by far the most | ||
| // common Gitea setup mistake. Scoped to tea so the phrase cannot misclassify another CLI. | ||
| (command === "tea" && normalized.includes("no available login")) |
There was a problem hiding this comment.
This changes backend failure classification (a tea exit mentioning "no available login" now surfaces as GiteaCliAuthenticationError rather than GiteaCliCommandError), but VcsProcess.test.ts has no case for it even though it already covers the analogous authentication and rate-limited phrases. Consider adding a focused case asserting failureKind: "authentication" for command: "tea" with that stderr, plus one asserting another command with the same phrase stays command-failed (the reason the rule is scoped to tea).
Related: no tea branch exists in the not-found block below, so classifyNonZeroExit never returns not-found for tea. GiteaCli.checkoutPullRequest's GiteaPullRequestNotFoundError.fromVcsError path is therefore only reachable in the unit test that hand-builds failureKind: "not-found"; a real tea pulls checkout 9999 failure maps to GiteaCliCommandError. Worth either adding the tea not-found phrases here or dropping the unreachable branch.
Posted via Macroscope — Effect Service Conventions
MDragonryu
commented
Aug 25, 2026
Follow-up fixes pushed in commit d257b11:\n\n- full PR URL checkout now verifies the URL repository against the current Gitea repository before invoking tea, preserving same-repository and numeric checkout behavior\n- public github.com and bitbucket.org /pulls/ URLs are excluded from the generic Gitea reference parser while self-hosted lookalikes remain supported\n- added regression coverage for the repository guard and URL parsing\n\nVerification: 8 focused Linux devcontainer test files, 168 tests passed; targeted server/web TypeScript checks completed. |
There was a problem hiding this comment.
One new finding on the Gitea CLI error model, plus one earlier finding that is still open (see below). Everything else in the Gitea service/provider pair follows the conventions: namespaced effect/* imports, Context.Service with an inline interface, exported make/layer, dependencies acquired via yield* VcsProcess.VcsProcess / yield* GiteaCli.GiteaCli, structured Schema.TaggedErrorClass failures with real causes preserved and messages derived from stable attributes, and Schema.Union + Schema.is for the error predicate.
Still open from a previous run (not re-commented): apps/server/src/vcs/VcsProcess.ts changes non-zero-exit classification so a tea failure mentioning "no available login" is now categorized as authentication, and apps/server/src/vcs/VcsProcess.test.ts still has no case covering it (nor a case proving another command with the same phrase stays command-failed). Backend behavior changes should carry focused tests.
Posted via Macroscope — Effect Service Conventions
Uh oh!
There was an error while loading. Please reload this page.
MDragonryu
commented
Aug 25, 2026
Follow-up test/convention fixes pushed in commit 556ce2d:\n\n- removed the redundant single-value Gitea pull-request decode operation field\n- added focused coverage for tea no-available-login authentication classification and the non-tea negative case\n\nVerification: 9 focused Linux devcontainer test files, 182 tests passed. |
There was a problem hiding this comment.
Reviewed the changed web UI surfaces for the Gitea provider addition (CommandPalette.tsx, GitActionsControl.tsx, SourceControlSettings.tsx, sourceControlPresentation.ts, pullRequestLinkContextMenu.ts, pullRequestDetail.logic.ts, openPullRequestLink.ts, pullRequestReference.ts). Provider enumerations, labels, icons and readiness maps are consistent, and the reference parser gap flagged on the earlier revision is now closed.
Two findings, one behavioral:
openPullRequestLink.tsnow claims Gitea/pulls/{n}URLs, which makes those links open the in-app change-request surfaces even though this build registers no Gitea pull-request provider — the reader lands on the "Could not load pull requests" empty state instead of the host page they previously got in the browser.GitActionsControl.tsxuses the literal"gitea"as the publish-dialog host, which is rendered as a hostname prefix when discovery reports no host.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
MDragonryu
commented
Aug 25, 2026
Final review cycle for 556ce2d is complete. Cursor Bugbot, Macroscope Correctness, Effect Service Conventions, and UI Consistency are green; the Approvability check completed neutral with correctness checked and eligibility unchecked. No new inline comments were posted on the current head. The worktree is clean and the PR is ready for human review. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 16d32bc. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Two findings, both in the Source Control settings copy that this PR reworked to make room for Gitea's tea login add hint. The rest of the Gitea UI wiring (neutral GitPullRequestIcon for the unbranded provider, resolvePublishHost host fallback, /pulls/ link handling) looks consistent with the existing provider patterns.
Posted via Macroscope — UI Consistency
Uh oh!
There was an error while loading. Please reload this page.
| if (executable !== null) { | ||
| return `${label} is not authenticated on this server. Run \`${executable} login add\` on the server host to enable change request features.`; |
There was a problem hiding this comment.
login add is a tea-only subcommand, but this helper renders the unauthenticated row for every provider, so GitHub now reads "Run gh login add", GitLab "Run glab login add" and Azure DevOps "Run az login add" — none of those commands exist (gh auth login, glab auth login, az login). The copy that was replaced was deliberately executable-agnostic.
Consider keeping the neutral phrasing here (or threading a per-provider auth command through the discovery spec) so only Gitea gets the tea login add wording:
| if(executable!==null){ | |
| return`${label} is not authenticated on this server. Run \`${executable} login add\` on the server host to enable change request features.`; | |
| return`${label} is not authenticated on this server. Sign in or configure credentials using the \`${executable}\` tool on the server host to enable change request features.`; |
Posted via Macroscope — UI Consistency
t3dotgg
commented
Aug 28, 2026
Note 🤖 GPT-5.6 Sol responding on behalf of Theo We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together. We do not have a current support commitment for Gitea. This 39-file branch would add a source control CLI, discovery, repository operations, settings, contracts, icons, and a permanent compatibility obligation. If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed. |

Summary
teaTesting
CI=true pnpm exec vp test run apps/server/src/sourceControl/GiteaCli.test.ts apps/server/src/sourceControl/GiteaSourceControlProvider.test.ts apps/server/src/sourceControl/giteaLogins.test.ts apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts packages/shared/src/sourceControl.test.ts packages/contracts/src/sourceControl.test.tsNotes
Gitea support uses the authenticated
teaCLI and preserves the existing provider architecture across server and clients.Note
Medium Risk
Changes git stacked actions, PR creation/deduping, and remote provider resolution; behavior is heavily tested but errors could still mis-route remotes or mishandle
teaHTTP responses.Overview
Adds first-class Gitea support end-to-end so remotes no longer resolve to
unknownand Commit, push & create PR works against Gitea hosts.On the server, a new
GiteaClilayer wraps theteatool (API calls with HTTP status parsing becausetea apiexits 0 on errors, client-side PR list filtering/pagination, repo publish routing touser/reposvsorgs/.../repos).GiteaSourceControlProviderplugs into the existing registry with CLI discovery fromtea logins listand host-based refinement of otherwise-unknown self-hosted remotes whenteais logged into that host.Contracts, shared remote detection (
gitea.com/*gitea*hosts), web and mobile add-project/publish/settings flows, and docs are extended forgitea. Publish UI uses discovery for hostname when there is no canonical host. In-app deep links still skip Gitea (parseChangeRequestUrldoes not claim/pulls/{n}), while checkout/reference parsing accepts Gitea URLs andtea pulls checkout.VcsProcessnow treatstea’s “no available login” stderr as an authentication failure.Reviewed by Cursor Bugbot for commit d1b9069. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add Gitea source-control provider across backend, web, and mobile
GiteaCliwrapper around theteaCLI in GiteaCli.ts, covering PR list/get/create, repo creation, clone URLs, default branch, and checkout, with typed errors and HTTP status mapping.GiteaSourceControlProviderin GiteaSourceControlProvider.ts implementing the provider contract, plus discovery and auth parsing based ontea logins listoutput.'gitea'literal added toSourceControlProviderKindschema in sourceControl.ts; out-of-tree contract consumers must regenerate schemas to decode the new kind.Macroscope summarized d1b9069.