fix(tangled_pr): POST the htmx /pulls/new form, not /pulls/
/pulls/ is 405. The appview's PR form is htmx: it only processes the create when it sees HX-Request, otherwise it re-renders the page — a 200 that silently creates nothing. Success comes back as HX-Redirect (…/pulls/<n>), not a 3xx Location, so the old redirect check never fired. Also send source=branch + the title/body Dirty flags the form expects, and point the auth-failure hint at the current cookie path. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWTdUq2bsic7JZGqJp3nD6
This commit is contained in:
+25
-15
@@ -49,17 +49,26 @@ def main():
|
||||
a = ap.parse_args()
|
||||
|
||||
cookie = load_cookie(a.cookie_file)
|
||||
url = f"{BASE}/{a.owner}/{a.repo}/pulls/"
|
||||
form = {"targetBranch": a.target, "sourceBranch": a.source}
|
||||
# Tangled's PR form is htmx: it POSTs to /pulls/new (NOT /pulls/, which is 405) and only processes
|
||||
# the create when it sees the HX-Request header — otherwise it just re-renders the page (a 200 that
|
||||
# creates nothing). Success is signalled by an HX-Redirect header pointing at the new pull.
|
||||
new_url = f"{BASE}/{a.owner}/{a.repo}/pulls/new"
|
||||
form = {
|
||||
"source": "branch", # branch-compare mode (each PR targets the branch below it)
|
||||
"targetBranch": a.target,
|
||||
"sourceBranch": a.source,
|
||||
"title": a.title, "titleDirty": "true",
|
||||
"body": a.body, "bodyDirty": "true",
|
||||
}
|
||||
if a.fork: form["fork"] = a.fork
|
||||
if a.title: form["title"] = a.title
|
||||
if a.body: form["body"] = a.body
|
||||
data = urllib.parse.urlencode(form).encode()
|
||||
req = urllib.request.Request(url, data=data, method="POST", headers={
|
||||
req = urllib.request.Request(new_url, data=data, method="POST", headers={
|
||||
"Cookie": cookie,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "tangled-pr-bot",
|
||||
"Referer": f"{BASE}/{a.owner}/{a.repo}/pulls/new",
|
||||
"HX-Request": "true",
|
||||
"HX-Current-URL": new_url,
|
||||
"Referer": new_url,
|
||||
})
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
@@ -68,17 +77,18 @@ def main():
|
||||
opener = urllib.request.build_opener(NoRedirect)
|
||||
try:
|
||||
r = opener.open(req, timeout=90)
|
||||
code, loc, body = r.getcode(), r.headers.get("Location", ""), r.read(3000).decode("utf-8", "replace")
|
||||
hdrs, code, body = r.headers, r.getcode(), r.read(3000).decode("utf-8", "replace")
|
||||
except urllib.error.HTTPError as e:
|
||||
code, loc, body = e.code, e.headers.get("Location", ""), e.read(3000).decode("utf-8", "replace")
|
||||
|
||||
print(f"HTTP {code}" + (f" -> {loc}" if loc else ""))
|
||||
if code in (301, 302, 303, 307, 308) and "/pulls/" in loc and "/new" not in loc:
|
||||
print("OK: pull created ->", (BASE + loc) if loc.startswith("/") else loc)
|
||||
hdrs, code, body = e.headers, e.code, e.read(3000).decode("utf-8", "replace")
|
||||
# htmx success is signalled by HX-Redirect (…/pulls/<n>), not a normal 3xx Location.
|
||||
target = hdrs.get("HX-Redirect", "") or hdrs.get("HX-Location", "") or hdrs.get("Location", "")
|
||||
print(f"HTTP {code}" + (f" -> {target}" if target else ""))
|
||||
if target and "/pulls/" in target and "/new" not in target:
|
||||
print("OK: pull created ->", (BASE + target) if target.startswith("/") else target)
|
||||
return
|
||||
if code in (301, 302, 303) and ("/login" in loc or "oauth" in loc):
|
||||
sys.exit("AUTH FAILED: session cookie expired/invalid — redo the one-time browser login "
|
||||
"and update tools/.tangled-session")
|
||||
if ("/login" in target or "oauth" in target.lower()):
|
||||
sys.exit("AUTH FAILED: session cookie expired/invalid — refresh engine/.tangled-session "
|
||||
"(scripts/get-tangled-cookie.py)")
|
||||
# otherwise surface whatever the page said (a Notice, etc.)
|
||||
snippet = " ".join(body.split())[:600]
|
||||
print(" no clear success redirect — response snippet:")
|
||||
|
||||
Reference in New Issue
Block a user