fix(compaction): validate retry_in backoff before rendering the retry note

updateCompactionProgress coerced evt.retry_in with Number() and rendered it
unguarded, while the sibling part/total path two lines below is finiteness-
validated — a malformed backoff would render "retrying in NaNs". Validate
retry_in the same way (finite, non-negative), and keep the error text
regardless: the error is the load-bearing half of the note, so an unparseable
duration drops to "retrying (error)…" rather than suppressing the whole arm.

Addresses PR review feedback on the compaction reducer.
This commit is contained in:
Patrick Buckley
2026-07-17 10:34:27 -07:00
parent abe053f507
commit 515d372a14
2 changed files with 23 additions and 5 deletions
+14
View File
@@ -168,3 +168,17 @@ def test_unbounded_render_inputs_are_capped() -> None:
assert "more preview lines not shown" in body assert "more preview lines not shown" in body
assert "RAW_CAP" in body assert "RAW_CAP" in body
assert "truncated for display" in body assert "truncated for display" in body
def test_retry_note_validates_backoff_before_rendering() -> None:
"""retry_in is a server-emitted backoff coerced with Number(); like the
part/total pair just below it, it must be finiteness-validated (and
non-negative) so a malformed value can't render "retrying in NaNs". The
error text is kept regardless — it is the load-bearing half of the note."""
body = _body()
assert "Number.isFinite(secs) && secs >= 0" in body, (
"retry_in must be validated (finite, non-negative) before its seconds render"
)
assert "Math.round(Number(evt.retry_in))" not in body, (
"retry_in must not be Math.round(Number(...))'d without a finiteness guard"
)
+9 -5
View File
@@ -169,12 +169,16 @@ export function updateCompactionProgress(el, evt) {
return; return;
} }
if (evt.retry_in != null) { if (evt.retry_in != null) {
// retry_in is a server-emitted backoff (seconds) coerced with Number();
// validate it the way part/total below are, so a malformed value can't
// render "retrying in NaNs". The error text is the load-bearing half —
// keep it whether or not the duration parses.
const secs = Number(evt.retry_in);
const err = String(evt.error || "error");
note.textContent = note.textContent =
"retrying in " + Number.isFinite(secs) && secs >= 0
Math.round(Number(evt.retry_in)) + ? "retrying in " + Math.round(secs) + "s (" + err + ")…"
"s (" + : "retrying (" + err + ")…";
String(evt.error || "error") +
")…";
return; return;
} }
const part = Number(evt.part); const part = Number(evt.part);