A GITEA_TOKEN secret stored with a trailing newline makes curl emit
`Authorization: token <value>\n`. The bare LF terminates the header
block early, so Content-Type and everything after it is parsed as
request body and Gitea rejects the call with 422 "Empty Content-Type".
GETs were unaffected because nothing they need follows the auth header,
which is why this presented as a proxy stripping the header.
- trim CR/LF and surrounding whitespace off GITHUB_TOKEN, GITEA_TOKEN,
GITEA_URL and GITEA_OWNER, warning when a value needed it
- rebuild redact() on bash substitution; the previous sed s/// blew up
with "unterminated `s' command" on a token containing a newline
- add AGENTS.md / CLAUDE.md documenting the run flow and these traps
Runs from the GitHub runner were failing every migrate with 422
"[]: Empty Content-Type" while the same script worked from a local
network, so the header is being dropped in front of Gitea on the
public path rather than the payload being wrong.
- route POST/PATCH through one gitea_write helper: body via
--data-binary @file (also keeps the PAT out of the process list),
Expect: disabled, explicit charset, retry over HTTP/1.1 when the
response still reports an empty Content-Type
- add DEBUG_HTTP=1 to dump request/response headers, tokens redacted
- move temp files into a mktemp -d cleaned by trap
- add a workflow preflight that POSTs JSON over HTTP/2 and HTTP/1.1 to
show whether the header survives the trip
- drop the redundant chmod; the script is committed mode 100755