docs(solutions): document Gitea PR-creation workflow (gh/tea limits -> REST API)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Creating pull requests on the self-hosted Gitea remote
|
||||
date: 2026-06-27
|
||||
category: tooling-decisions
|
||||
module: dev-workflow / gitea
|
||||
problem_type: tooling_decision
|
||||
component: development_workflow
|
||||
severity: low
|
||||
applies_when:
|
||||
- Opening or editing a PR for this repo from a non-interactive agent/CI shell
|
||||
- "`gh pr create` fails with: none of the git remotes point to a known GitHub host"
|
||||
- tea exits with "Failed to read SSH passphrase ... could not open TTY"
|
||||
tags: [gitea, pull-request, tea, gh, rest-api, ssh, dotenv]
|
||||
---
|
||||
|
||||
# Creating pull requests on the self-hosted Gitea remote
|
||||
|
||||
## Context
|
||||
|
||||
This repo's `origin` is a **self-hosted Gitea** instance, not GitHub:
|
||||
|
||||
- API/web base: `https://git.gorinskat.nl` (owner `m038`, repo `immich-photo-flow`)
|
||||
- Push remote (SSH): `ssh://git@m038-nas.tail63ee39.ts.net:222/m038/immich-photo-flow.git` — a *different host* than the API, reverse-proxied behind nginx.
|
||||
|
||||
Opening a PR from an agent/non-interactive shell fails through the two obvious tools, which wastes a lot of back-and-forth if you don't know why.
|
||||
|
||||
## Guidance
|
||||
|
||||
Open PRs via the **Gitea REST API over HTTPS**, authenticated with a token from `.env`. Do not rely on `gh` or `tea` from a non-interactive shell.
|
||||
|
||||
1. Make sure the **base branch already exists on the server** — a PR needs it. (Gitea sets the repo default branch to the *first* branch you push, so push your base branch, e.g. `main`/`master`, before or alongside the feature branch.)
|
||||
2. Read `GITEA_HOST`, `GITEA_USER`, `GITEA_TOKEN` from `.env` (a `write:repository`-scoped token). **Never print, echo, or commit `.env`** — `source` it; keep the token out of `argv` (use a `curl -K` config file, mode 0600) and shred the file after.
|
||||
3. `POST {base}/api/v1/repos/{GITEA_USER}/{repo}/pulls` with `{head, base, title, body}`.
|
||||
|
||||
```bash
|
||||
set -a; . .env; set +a # never cat/echo this file
|
||||
case "$GITEA_HOST" in *://*) base="$GITEA_HOST";; *) base="https://$GITEA_HOST";; esac
|
||||
umask 077; cfg=$(mktemp); printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$cfg"
|
||||
# body via file -> JSON payload (avoids backtick/$ re-evaluation in the body)
|
||||
python3 - <<'PY'
|
||||
import json; json.dump({"head":"feat/my-branch","base":"main",
|
||||
"title":"feat: ...","body":open("/path/to/body.md").read()}, open("/tmp/pr.json","w"))
|
||||
PY
|
||||
curl -sS -K "$cfg" -X POST -H "Content-Type: application/json" --data @/tmp/pr.json \
|
||||
"${base%/}/api/v1/repos/$GITEA_USER/$repo/pulls" -w '\nHTTP %{http_code}\n'
|
||||
shred -u "$cfg"
|
||||
```
|
||||
|
||||
Repo-level settings use the same API, e.g. set the default branch:
|
||||
`PATCH {base}/api/v1/repos/{owner}/{repo}` with `{"default_branch":"main"}` (only do this with explicit user consent — it's a persistent change to shared infra).
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- **`gh` is GitHub-only.** It errors `none of the git remotes ... point to a known GitHub host` and cannot target a Gitea instance.
|
||||
- **`tea` (0.14) can't authenticate non-interactively here.** Its login is configured with an SSH key that has a passphrase, and it insists on reading the passphrase from `/dev/tty` — even with `ssh_agent: true` and the key already loaded in the agent. From an agent/CI shell there is no TTY, so it dies with `could not open TTY`. (Plain `git`-over-SSH still works because the agent answers the key; only `tea`'s own auth flow needs the TTY.) The user *can* run `tea` in their own terminal — it only fails for non-interactive callers.
|
||||
- The REST API needs neither GitHub nor a TTY, so it's the reliable path for automation.
|
||||
|
||||
## When to Apply
|
||||
|
||||
- Any time an agent needs to open or edit a PR (or change repo settings) on this Gitea remote.
|
||||
- Generalizes to any non-GitHub Gitea/Forgejo remote reached from a non-interactive shell.
|
||||
|
||||
## Examples
|
||||
|
||||
- **Symptom → cause:** `gh pr create` → wrong forge; `tea pulls create` → `Failed to read SSH passphrase: could not open TTY` → use the REST API instead.
|
||||
- **Gotcha:** after pushing only a feature branch first, the repo default branch became the feature branch; pushing `master` gave the PR a base, and a later `PATCH default_branch` fixed the default.
|
||||
|
||||
## Related
|
||||
|
||||
- Credentials live in `.env` (gitignored): `GITEA_HOST`, `GITEA_USER`, `GITEA_TOKEN` — documented in `.env.example`.
|
||||
- The two repo paths `~/Projects/immich-photo-flow` and `~/Nextcloud/Projects/immich-photo-flow` are the same repo (`~/Projects` is a symlink), not two clones.
|
||||
Reference in New Issue
Block a user