Files
command-center/agent/managed-skills/gitea-merge-form-encoded/SKILL.md
T

6.0 KiB

name, description
name description
gitea-merge-form-encoded Use when a Gitea API merge on Dankclaw (192.168.0.143:3000) returns HTTP 422 "[Do]: Required" or 405 "Please try again later" — the merge endpoint needs form-encoded fields with the lowercase field name do, and a behind-main branch must be merged forward before it will merge.

Gitea on Dankclaw (192.168.0.143:3000) — merge quirks, measured 2026-10-19 on dank/serverAppConfigs and again on dank/dankvoice, re-earned the same day on serverAppConfigs PR #27.

1. The payload: form-encoded, lowercase do

Creating a PR accepts a JSON body. Merging does not: POST /api/v1/repos/{owner}/{repo}/pulls/{n}/merge with a JSON body ({"merge_style":"no-ff", ...}) or with those values as request headers returns 422 {"message":"[Do]: Required"} — four variants of that all fail identically, so do not "fix" it by changing the style name or moving the headers around.

The field name is lowercase do. --data-urlencode 'Do=merge' (capital D, which the error message spells) returns the same 422 [Do]: Required. Do not retry with case variants — the web-UI form field is do.

Working call (form-encoded):

tok=$(sed -n 's|^https\{0,1\}://[^:]*:\([^@]*\)@192\.168\.0\.143.*$|\1|p' ~/.config/secrets/gitea | head -1)   # git credential.helper store file
curl -sS -X POST -w 'http %{http_code}\n' -K <(printf 'header = "Authorization: token %s"\n' "$tok") \
  --data-urlencode 'do=merge' \
  --data-urlencode 'merge_style=no-ff' \
  http://192.168.0.143:3000/api/v1/repos/dank/<repo>/pulls/<N>/merge
# 200 with an empty body == merged
  • merge_style values are the dashed names: no-ff, merge, rebase, squash.
  • Keep the token out of argv: -K <(printf …) feeds the Authorization header through stdin (a -H "Authorization: token …" lands in /proc/<pid>/cmdline, world-readable — the 2026-09-16 HF-token leak class).
  • Verify by fast-forwarding a checkout (git fetch && git merge --ff-only origin/main) rather than re-reading the PR.
  • Fleet law still applies: never merge without an explicit current OK from Frank, and open the PR URL in his browser (xdg-open) the moment it exists.

2. 405 {"message":"Please try again later"} = the branch is behind main

A correctly formed merge call returns 405, not 422, when Gitea cannot test-merge the head into the base. Read it as "merge main forward", not "retry later" — no sleep-retry ever clears it.

Confirm first: GET /pulls/{n} shows "mergeable": false (and merged: false, state: open) for exactly this state.

The fix is the repo's own trunk-based law — merge forward, never rebase:

git worktree add ../wt-prN -B prN <head-branch>
cd ../wt-prN && git merge --no-edit origin/main      # resolve, see below
./tests/run.sh                                       # the seam is the arbiter, run it on the RESOLVED tree
git push -q origin prN:refs/heads/<head-branch>      # same branch name; Gitea reuses the open PR
# then re-POST the merge — 200 this time

Notes earned on PR #27 (2026-10-19):

  • A git worktree per PR keeps the deployed checkout untouched; clean up with git worktree remove --force ../wt-prN && git branch -D prN.
  • git status --short during a merge lists every incoming file as staged — that is the merge commit's content, not a dirty tree.
  • Push a local branch to the PR's branch name with an explicit refspec (prN:refs/heads/docs/…); the push output prints Visit the existing pull request, which is how you confirm you hit the right head.
  • Gitea needs a few seconds after the push to recompute mergeability; a sleep 3 before re-POSTing avoids a second 405.

3. Resolving vault conflicts against a compaction wave

When main has run a docs compaction (page deletions, description shortening), two conflict shapes recur:

  • description: — scripts/check-okf.py enforces DESC: ≤ 120 chars. A branch's longer description must be shortened, not argued for; keep main's shape and fold the new topic into the wording.
  • A [[Page]] link whose target page main destroyed — git ls-tree -r --name-only origin/main -- serverAppConfigs-docs | grep -i '<page>' settles it. Drop the dead link; do not resurrect a page. Compaction left link-stripped stub bullets behind (- the compile fix for …, dash + two spaces): git grep -nE '^- [A-Z]' origin/main -- 'serverAppConfigs-docs/**/*.md' counts them, and the seam cannot see them because a stub is no longer a link to validate.

After any frontmatter change, regenerate: ./scripts/gen-vault-index.py serverAppConfigs-docs and commit the index rows — the vault indices are generated, not hand-carried seam diffs regeneration against the checkout. The sub-index sync can emit one extra space after | for a quoted description; commit what the generator emits, since the seam compares against the generator.

Credential-store token extraction (same file, adjacent trap)

~/.config/secrets/gitea holds one line shaped http://user:token@192.168.0.143:3000. Splitting it with awk -F'/' puts the host in $3, not $2, and awk -F'/' '{print $NF}' yields the host+path, not the token — an empty/garbage token makes private repos answer 404 not found (Gitea hides them from anonymous), which reads like a wrong URL rather than an auth failure. Extract with:

tok=$(sed -n 's|^https\{0,1\}://[^:]*:\([^@]*\)@192\.168\.0\.143.*$|\1|p' ~/.config/secrets/gitea | head -1)
echo "token length: ${#tok}"   # 40 — print the length, never the value

Other API quirks measured the same day:

  • GET /pulls?state=open can return an error object where state=all returns the list; state=all list entries may omit head/base — fetch /pulls/{n} for those.
  • POST /pulls for a head/base pair that already has a PR → 409 naming issue_id: N; the PR number is that issue_id, so PATCH it instead of POSTing again.
  • Issues and PRs share the issue index per repo, so #36-style numbers are repo-scoped: a footer fix aimed at dank/command-center#36 404s when pointed at dank/dankvoice.