Files

4.3 KiB

title, date, category, module, problem_type, component, severity, applies_when, tags
title date category module problem_type component severity applies_when tags
Creating pull requests on the self-hosted Gitea remote 2026-06-27 tooling-decisions dev-workflow / gitea tooling_decision development_workflow low
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"
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 .envsource 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}.
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 createFailed 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.
  • 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.