live: heartbeat sync 2026-10-18T18:51:41-07:00 (Refs #11)
This commit is contained in:
1 parent
7de07ee35c
commit
0ee08bc368
11 files changed
+201
-209
No files matched your search
@@ -1,105 +1,31 @@
|
||||
---
|
||||
name: gitea-api-pr-operations
|
||||
description: "Open, list, merge, and verify pull requests (and diagnose issue-API scope failures) on the Dankclaw Gitea via its REST API from a workstation, keeping the token out of argv (curl -K config file, mode 600) — includes the credential-store extraction one-liner, the merge endpoint's Do-field law and Content-Type-header trap, the suggested-URL-is-not-a-PR gotcha, and the insufficient-token-scope trap."
|
||||
description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea API (192.168.0.143:3000, dank/command-center) with curl + token — merge payload quirk included"
|
||||
---
|
||||
|
||||
# Gitea API PR operations (Dankclaw)
|
||||
# Gitea API PR operations on Dankclaw
|
||||
|
||||
`git push` on this fleet prints `Create a new pull request for ...` — that is a
|
||||
**suggestion, not a PR**. Verify (`GET /pulls?state=open` → expect `[]`) before
|
||||
claiming a PR "should exist"; create it via the API when asked.
|
||||
|
||||
## Endpoint + auth
|
||||
- API base: `http://192.168.0.143:3000/api/v1/repos/dank/<repo>`
|
||||
- Token lives in the git credential store configured by
|
||||
`git config --get credential.helper` → `store --file ~/.config/secrets/gitea`,
|
||||
line format `http://user:TOKEN@192.168.0.143:3000`.
|
||||
- The repo-local `.env` `GITEA_TOKEN` (main checkout, gitignored) carries
|
||||
**`read:issue` too** (measured 2026-10-18): filing issues needs THAT token,
|
||||
the credential-store one 403s on issues.
|
||||
|
||||
Extract the token (the `http://` scheme contains a colon — scheme-stripping
|
||||
awk regexes silently grab the port instead; use this anchored sed):
|
||||
Verified 2026-10-18 on Gitea at `http://192.168.0.143:3000`, repo `dank/command-center` (owner `dank`).
|
||||
|
||||
## Token handling (never echo)
|
||||
```bash
|
||||
tok=$(sed -nE 's#^http://[^:/@]+:([^@]+)@192\.168\.0\.143.*#\1#p' ~/.config/secrets/gitea | head -1)
|
||||
[ -n "$tok" ] && echo "token-extracted(len=${#tok})" # length check, never echo the value
|
||||
cfg=$(mktemp) && chmod 600 "$cfg" && printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg"
|
||||
# use: curl -sS -K "$cfg" ... then rm -f "$cfg"
|
||||
```
|
||||
|
||||
## Keep the token out of argv (repo secret law)
|
||||
`curl -H "Authorization: token $tok"` is world-readable in `/proc/<pid>/cmdline`.
|
||||
Use a mode-600 config file:
|
||||
## Endpoints that work
|
||||
- Open PR: `POST /api/v1/repos/dank/command-center/pulls` with JSON `{title, head, base, body}` (build with `jq -n --arg`; `head` = branch name as-is, slashes included).
|
||||
- Patch PR (title/body): `PATCH .../pulls/<N>` — works, returns 201 with full PR JSON (`mergeable` field handy).
|
||||
- **Merge PR: `POST .../pulls/<N>/merge` with body `{"Do":"merge"}`** — capital-D `Do` is this Gitea's field (mirrors web UI form). `{}` or `{"DoNotMerge":false,...}` → 422 `[Do]: Required`. Success may return EMPTY body — verify with a separate `GET .../pulls/<N>` for `{state:"closed", merged:true, merge_commit_sha}`.
|
||||
- Issues: `POST .../issues` `{title, body}`; patch `PATCH .../issues/<N>`.
|
||||
- Issue/PR bodies MUST end with the AGENTS.md footer: `---` line, then `cwd: <wd> · session: <id>`. Embed the footer inside the body string (a bare `---` as a separate shell arg breaks `jq` arg parsing).
|
||||
- `xdg-open <url>` lands the PR/issue in Frank's Firefox — do it the moment a PR opens.
|
||||
|
||||
```bash
|
||||
cfg=$(mktemp); chmod 600 "$cfg"
|
||||
printf 'header = "Authorization: token %s"\n' "$tok" > "$cfg"
|
||||
```
|
||||
## Gotchas
|
||||
- POST/PATCH need `-H 'Content-Type: application/json'` on the curl command line; `-d @file` alone with a wrong content type yields 422 `"model.assignUsers: Bind is required"`.
|
||||
- Merge-with-empty-response is normal; never conclude failure from blank stdout — GET the PR.
|
||||
- No `tea` CLI anywhere; API + curl is the only path. GITEA_TOKEN env exists in `~/.omp/.env` (gitignored) as alternative source.
|
||||
|
||||
- POST JSON needs a second header line: `header = "Content-Type: application/json"`.
|
||||
There IS NO `content-type` config option — curl rejects the file.
|
||||
- `rm -f "$cfg"` when done (a mode-600 token file left in /tmp is its own leak).
|
||||
|
||||
## Operations
|
||||
```bash
|
||||
# list open PRs
|
||||
curl -sS -K "$cfg" ".../pulls?state=open" | jq -r '.[] | [.number,.head.name,.base.name,.title] | @tsv'
|
||||
# create (build the payload as a FILE, see below)
|
||||
curl -sS -K "$cfg" -o /tmp/pr.json -w '%{http_code}\n' -X POST -d @/tmp/pr-payload.json ".../pulls"
|
||||
# verify: 201 is not enough — check mergeable
|
||||
curl -sS -K "$cfg" ".../pulls/1" | jq '{number,state,merged,mergeable,base:.base.ref,head:.head.ref,url:.html_url}'
|
||||
```
|
||||
|
||||
## Merging: the field is `Do`, and it is a STRING naming the strategy
|
||||
|
||||
Measured 2026-10-18 on Gitea at 192.168.0.143:3000 (`POST .../pulls/<n>/merge`).
|
||||
`MergePullRequestOption` is `{From, To, When, Do}` — **there is no `DoMerge` and
|
||||
no `merge_method`**. Every wrong shape is a 422 with a distinct message, so read it:
|
||||
|
||||
|body sent|http|message|
|
||||
|---|---|---|
|
||||
|`{"DoMerge":true,"merge_method":"merge"}`|422|`[Do]: Required`|
|
||||
|`{"Do":true,"merge_method":"merge"}`|422|`[]: json: cannot unmarshal JSON boolean into Go string within "/Do"`|
|
||||
|`{"Do":"true"}`|422|`[Do]: In…` (validation: not a strategy name)|
|
||||
|form body `Do=true&merge_method=merge`|422|`[Do]: Required` (the value must be a strategy name)|
|
||||
|`{"Do":"merge"}`|**200**|merged|
|
||||
|
||||
```bash
|
||||
curl -sS -K "$cfg" -H 'Content-Type: application/json' -X POST -d '{"Do":"merge"}' ".../pulls/4/merge"
|
||||
# then prove it: .merged true, .state closed, .merge_commit_sha set, and origin/main moved
|
||||
curl -sS ".../pulls/4" | jq -c '{number,state,merged,merge_commit_sha}'
|
||||
```
|
||||
|
||||
Strategy names: `merge`, `rebase`, `rebase-merge`, `squash`, `manually` — a
|
||||
strategy the repo does not allow is rejected too.
|
||||
|
||||
**Merge trap (measured 2026-10-18):** on `POST /merge` the `Content-Type` header
|
||||
must go on the **command line** (`-H 'Content-Type: application/json'`). Same
|
||||
curl that succeeds on `POST /pulls` with Content-Type supplied only via the `-K`
|
||||
config file returns **405 with an EMPTY body** on `/merge`. A 405 there is this
|
||||
trap (or a base-moved mergeability recheck — GET the PR, see `mergeable:true`,
|
||||
then retry with `-H`), NOT a permission problem.
|
||||
|
||||
## Building issue/PR bodies without quote carnage
|
||||
Hand-written `jq -n --arg t "..." '{title:$t, body:"..."}'` one-liners with long
|
||||
markdown bodies WILL break on a stray quote (and the broken quoting can swallow
|
||||
the following commands). Build bodies as files (heredoc or write tool), then:
|
||||
```bash
|
||||
b=$(jq -Rs . body.md)
|
||||
jq -n --arg t "$title" --argjson b "$b" '{title:$t, body:$b, head:"<branch>", base:"main"}' > payload.json
|
||||
curl ... -d @payload.json
|
||||
```
|
||||
|
||||
## Gotchas measured 2026-10-14 / 2026-10-17 / 2026-10-18
|
||||
- Unauthenticated/bad token → HTTP 401 body `{"message":"not found"}`; `jq '.[]|[.number,..]'` then fails with *cannot index "not found"* — that is an auth failure, not a missing repo.
|
||||
- **Insufficient token scope ≠ missing data.** The credential-store token
|
||||
(measured 2026-10-17) carries only `write:repository,write:user`; the issues
|
||||
API answers HTTP 403 `{"message":"token does not have at least one of required
|
||||
scope(s), required=[read:issue], token scope=write:repository,write:user"}`.
|
||||
jq surfaces that JSON as the *cannot index* error — do NOT conclude the issue
|
||||
list is empty, and note POSTs under this token create NOTHING (verify, don't
|
||||
assume retry safety). Filing issues needs a fresh token minted in the Gitea
|
||||
web UI with `read:issue`+`write:issue` (Frank-class credential action; ask) —
|
||||
OR use the main-checkout `.env` `GITEA_TOKEN`, which already has issue scope.
|
||||
PR endpoints work fine with either token (`pulls` POST/GET/merge all 200/201).
|
||||
- On the created-PR response `.head.name` can be null; verify with the `GET /pulls/<n>` and use `.head.ref`.
|
||||
- **Swagger JSON is disabled on this instance** — `/api/swagger.v1.json`, `/api/swagger.json` and `/api/v1/swagger.json` all 404. Do not go looking for the schema there; the 422 messages name the field and its Go type, which is the faster instrument.
|
||||
## Post-merge propagation
|
||||
The `~/.omp` checkout IS the deployment: `git -C ~/.omp fetch && git merge --ff-only origin/main` (dirty runtime files don't block ff). Never merge a PR at all without Frank's explicit OK.
|
||||
Reference in new issue
Block a user