5ef589f4bdca750ae44fabb5c648378e8264b2c9 max Tue Sep 15 04:56:51 2026 -0700 redmineCli: match the 403/404 prefix, not anywhere in the error text summarize_issue treats a ticket as unreadable when api_get reports 403 or 404, and re-raises everything else so a real failure is not hidden behind "(not readable)". The test looked for "HTTP 403 GET" anywhere in the message, but that message carries up to 500 characters of the server's response body, so a 500 or a proxy error page quoting those words in its own text was swallowed as well. Anchor it to the prefix the tool builds. diff --git src/utils/redmineCli src/utils/redmineCli index ca4f4f4396b..a7623b44b01 100755 --- src/utils/redmineCli +++ src/utils/redmineCli @@ -1191,36 +1191,46 @@ """Return a list of (other_ticket_id, relation_type) for a ticket's relations. The relation JSON names both ends (issue_id, issue_to_id); the "other" end is whichever one is not this ticket.""" data = api_get(base_url, f"/issues/{ticket_id}.json?include=relations", api_key) tid = int(ticket_id) rels = [] for r in data["issue"].get("relations", []): other = r["issue_to_id"] if r["issue_id"] == tid else r["issue_id"] rels.append((other, r.get("relation_type", "relates"))) # Stable order: by relation type, then ticket id. rels.sort(key=lambda t: (t[1], t[0])) return rels def summarize_issue(base_url, ticket_id, api_key): - """Return {id, subject, status, tracker} for a ticket, or None if it can't be read - (e.g. deleted or no permission).""" + """Return {id, subject, status, tracker} for a ticket, or None if the ticket itself + is not accessible (deleted -> 404, or no permission -> 403). Any other failure + (network trouble, rate limiting, bad API key) is re-raised, so it is not silently + hidden behind a "(not readable)" label.""" try: d = api_get(base_url, f"/issues/{ticket_id}.json", api_key)["issue"] - except SystemExit: + except SystemExit as e: + # api_get exits with "Error: HTTP <code> GET <url>: <body>", where body is up to 500 + # characters of whatever the server sent. Match the prefix we build, not anywhere in + # the string: a 502 or a proxy error page that happens to quote "HTTP 404 GET" in its + # own text would otherwise be swallowed and labelled "(not readable)", which is exactly + # the confusion this function exists to prevent. + msg = str(e) + if msg.startswith("Error: HTTP 403 GET ") or msg.startswith("Error: HTTP 404 GET "): return None + raise return {"id": d["id"], "subject": d.get("subject", ""), "status": d.get("status", {}).get("name", ""), "tracker": d.get("tracker", {}).get("name", "")} def related_lines(base_url, ticket_id, api_key): """Build markdown bullet lines describing a ticket's related tickets, one per line with tracker/status and subject. Returns [] if there are none.""" rels = fetch_relations(base_url, ticket_id, api_key) if not rels: return [] lines = [] for other_id, rtype in rels: info = summarize_issue(base_url, other_id, api_key) if info: