From dabedcf79ca891d6aba05ca6a2ce3e4a17dbbc94 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 12 Jul 2026 18:10:55 -0700 Subject: [PATCH] FN-7890: cap report-a-bug GitHub URL by encoded length, not raw body length Fixes the Report-a-Bug flow producing a GitHub "request URL too long" error by budgeting truncation against the actual encoded URL GitHub receives instead of the raw diagnostics body length. - Replace the raw BUG_URL_BODY_CAP (5500 chars) with BUG_URL_MAX_ENCODED (8000 chars), measured against the final GitHub issue URL (base URL + ?body= + encodeURIComponent(body)). - Add buildBugReportIssueUrl() which binary-searches the largest body prefix (by code point) whose encoded URL still fits the budget, appending a truncation marker when needed. - doReportBug now calls buildBugReportIssueUrl(body) instead of manually slicing the body and encoding it inline. - Update tests to assert the final URL length stays under BUG_URL_MAX_ENCODED and to exercise a diagnostics bundle whose JSON (quotes/braces) expands significantly under percent-encoding. Files changed: .../__tests__/SystemControlsArea.test.tsx | 9 +++-- .../command-center/areas/SystemControlsArea.tsx | 47 +++++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7890 Fusion-Task-Lineage: fcadafcd-0bbc-4c06-b2ec-c2b124d736db Co-authored-by: Fusion (runfusion.ai) --- .../__tests__/SystemControlsArea.test.tsx | 9 ++-- .../areas/SystemControlsArea.tsx | 47 +++++++++++++++---- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx b/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx index 41a69ce715..c61b479601 100644 --- a/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx +++ b/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import "@testing-library/jest-dom"; import { CommandCenter } from "../CommandCenter"; +import { BUG_URL_MAX_ENCODED } from "../areas/SystemControlsArea"; const apiMock = vi.fn(); const mockFetchSystemInfo = vi.fn(); @@ -266,6 +267,8 @@ describe("SystemControlsArea layout integration", () => { expect(body).toContain("
Diagnostics"); expect(body).toContain('"recentLogs"'); expect(body).toContain("boom"); + expect(body).not.toContain("\u2026(truncated)"); + expect(url.length).toBeLessThanOrEqual(BUG_URL_MAX_ENCODED); confirmSpy.mockRestore(); openSpy.mockRestore(); @@ -314,12 +317,12 @@ describe("SystemControlsArea layout integration", () => { openSpy.mockRestore(); }); - it("truncates an oversized diagnostics bundle in the bug report body with the truncation cap marker", async () => { + it("truncates an oversized diagnostics bundle against the encoded GitHub URL ceiling", async () => { mockFetchSystemLogs.mockResolvedValue({ entries: Array.from({ length: 100 }, (_, i) => ({ timestamp: "2026-07-12T00:00:00.000Z", level: "error" as const, - message: `error-line-${i}-${"x".repeat(100)}`, + message: `error-line-${i}-{\"quoted\":\"${"x".repeat(100)}\",\"nested\":{\"value\":\"${"y".repeat(100)}\"}}`, })), }); const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true); @@ -332,8 +335,8 @@ describe("SystemControlsArea layout integration", () => { await waitFor(() => expect(openSpy).toHaveBeenCalledTimes(1)); const url = openSpy.mock.calls[0]?.[0] as string; const body = decodeURIComponent(url.split("?body=")[1] ?? ""); + expect(url.length).toBeLessThanOrEqual(BUG_URL_MAX_ENCODED); expect(body).toContain("\u2026(truncated)"); - expect(body.length).toBeLessThanOrEqual(5500 + "\n\u2026(truncated)".length); confirmSpy.mockRestore(); openSpy.mockRestore(); diff --git a/packages/dashboard/app/components/command-center/areas/SystemControlsArea.tsx b/packages/dashboard/app/components/command-center/areas/SystemControlsArea.tsx index 320df4d57e..15ccd57db2 100644 --- a/packages/dashboard/app/components/command-center/areas/SystemControlsArea.tsx +++ b/packages/dashboard/app/components/command-center/areas/SystemControlsArea.tsx @@ -58,9 +58,34 @@ const BACK_ONLINE_RELOAD_DELAY_MS = 3000; // respawn, unsupervised restart that stopped) doesn't leave the panel polling // forever with every control disabled. const RESTART_WAIT_TIMEOUT_MS = 90_000; -const BUG_URL_BODY_CAP = 5500; +export const BUG_URL_MAX_ENCODED = 8000; +const BUG_URL_TRUNCATION_MARKER = "\n…(truncated)"; +const BUG_URL_BODY_QUERY_PREFIX = "?body="; const GITHUB_NEW_ISSUE_URL = "https://github.com/Runfusion/Fusion/issues/new"; +function buildBugReportIssueUrl(body: string): string { + const prefix = `${GITHUB_NEW_ISSUE_URL}${BUG_URL_BODY_QUERY_PREFIX}`; + const toUrl = (candidate: string) => `${prefix}${encodeURIComponent(candidate)}`; + const fullUrl = toUrl(body); + if (fullUrl.length <= BUG_URL_MAX_ENCODED) return fullUrl; + + const encodedBudget = BUG_URL_MAX_ENCODED - prefix.length; + const codePoints = Array.from(body); + let low = 0; + let high = codePoints.length; + while (low < high) { + const mid = Math.ceil((low + high) / 2); + const candidate = `${codePoints.slice(0, mid).join("")}${BUG_URL_TRUNCATION_MARKER}`; + if (encodeURIComponent(candidate).length <= encodedBudget) { + low = mid; + } else { + high = mid - 1; + } + } + + return toUrl(`${codePoints.slice(0, low).join("")}${BUG_URL_TRUNCATION_MARKER}`); +} + type RestartPhase = null | "waiting" | "back" | "timeout"; interface SystemControlsAreaProps { @@ -378,11 +403,16 @@ export function SystemControlsArea({ projectId, addToast }: SystemControlsAreaPr context for a first triage pass. Now doReportBug reuses the exact same buildDiagnostics() bundle that "Copy diagnostics" produces (health, runtime/system info, recent logs) and asks a single confirmation question - covering that whole bundle. The confirm gate, fenceSafe neutralization, and - BUG_URL_BODY_CAP truncation are preserved unchanged because this content is - still sent to a public github.com issue and must never be included without - explicit operator consent, must not let a log line break out of the fenced - code block, and must not produce an over-length URL. + covering that whole bundle. + + FNXC:SystemPanel 2026-07-12-18:41: + Requirement change (FN-7890): truncation is budgeted against the full URL that + GitHub receives: base issue URL + ?body= + encodeURIComponent(body). FN-7883's + diagnostics JSON includes quotes, braces, and newlines that expand during + percent-encoding, so the old raw body-length cap could still emit a request + URL too long for GitHub. The confirm gate, fenceSafe neutralization, and body + sections remain unchanged while the final window.open URL is kept under the + encoded ceiling. */ const doReportBug = useCallback( () => @@ -396,7 +426,7 @@ export function SystemControlsArea({ projectId, addToast }: SystemControlsAreaPr ), ); const diagnostics = includeDiagnostics ? await buildDiagnostics() : null; - let body = [ + const body = [ "### What happened", "", "", @@ -419,8 +449,7 @@ export function SystemControlsArea({ projectId, addToast }: SystemControlsAreaPr ] : []), ].join("\n"); - if (body.length > BUG_URL_BODY_CAP) body = `${body.slice(0, BUG_URL_BODY_CAP)}\n…(truncated)`; - window.open(`${GITHUB_NEW_ISSUE_URL}?body=${encodeURIComponent(body)}`, "_blank", "noopener"); + window.open(buildBugReportIssueUrl(body), "_blank", "noopener"); }), [buildDiagnostics, info, runAction, t], );