A corrupt or future ci_wait_started silently disables the CI wait budget #473

Closed
opened 2026-08-28 07:08:53 +00:00 by mph · 2 comments
Owner

Found triaging IW-437 (raised in phase 3 code review; pre-existing, deliberately not fixed there).

CiGate.startOfWait (core/model/CiGate.scala:352-361) has two ways to make the total timeout stop enforcing, both silent:

  1. UNPARSEABLE VALUE. A present but unparseable ci_wait_started is swallowed (catch case _: Exception => None) and replaced by now. Every resumed invocation then recomputes startedAtMs as approximately now, so the budget resets instead of expiring.

  2. FUTURE VALUE. A parseable but future ci_wait_started makes (now - startedAtMs) negative, which never exceeds timeoutMs.

Either yields an unbounded wait, with no warning to the operator. The wait that was supposed to be capped simply never caps.

AGE. This is unchanged from baseline 81826a3. That revision's startOfWait already carried the identical catch case _: Exception => None plus .getOrElse(now) fallback, and a far-future ci_deadline produced the same negative-elapsed result via (deadline - timeoutMs). IW-437 phase 3 renamed the field and changed how the start is derived; it neither introduced nor widened this behaviour. The scaladoc documents the fallback deliberately: "Absent, or unreadable, means this invocation is the beginning of the wait."

THE DECISION TO MAKE. Absent is legitimately the beginning of a wait. Present-but-corrupt is not the same thing, and neither is present-but-future. What should each do - fail loud, or fall back to a bounded value? The "a typo must fail loudly" principle IW-423 raised for --needs-attention argues for the former on a corrupt value.

RELATED. Nothing enforces the schema's declared date-time format on this field, which is how an unparseable value reaches the card in the first place - see the companion issue on timestamp validation. That makes case 1 reachable through an ordinary bad write, not only through file corruption.

Found triaging IW-437 (raised in phase 3 code review; pre-existing, deliberately not fixed there). CiGate.startOfWait (core/model/CiGate.scala:352-361) has two ways to make the total timeout stop enforcing, both silent: 1. UNPARSEABLE VALUE. A present but unparseable ci_wait_started is swallowed (catch case _: Exception => None) and replaced by `now`. Every resumed invocation then recomputes startedAtMs as approximately now, so the budget resets instead of expiring. 2. FUTURE VALUE. A parseable but future ci_wait_started makes (now - startedAtMs) negative, which never exceeds timeoutMs. Either yields an unbounded wait, with no warning to the operator. The wait that was supposed to be capped simply never caps. AGE. This is unchanged from baseline 81826a3. That revision's startOfWait already carried the identical `catch case _: Exception => None` plus `.getOrElse(now)` fallback, and a far-future ci_deadline produced the same negative-elapsed result via (deadline - timeoutMs). IW-437 phase 3 renamed the field and changed how the start is derived; it neither introduced nor widened this behaviour. The scaladoc documents the fallback deliberately: "Absent, or unreadable, means this invocation is the beginning of the wait." THE DECISION TO MAKE. Absent is legitimately the beginning of a wait. Present-but-corrupt is not the same thing, and neither is present-but-future. What should each do - fail loud, or fall back to a bounded value? The "a typo must fail loudly" principle IW-423 raised for --needs-attention argues for the former on a corrupt value. RELATED. Nothing enforces the schema's declared date-time format on this field, which is how an unparseable value reaches the card in the first place - see the companion issue on timestamp validation. That makes case 1 reachable through an ordinary bad write, not only through file corruption.
Author
Owner

The companion issue referenced above is #474 — enforce the schema's declared date-time format in ReviewStateValidator, for every timestamp field on the card.

The companion issue referenced above is #474 — enforce the schema's declared `date-time` format in `ReviewStateValidator`, for every timestamp field on the card.
Author
Owner

Fixed in PR #501 (merge d1eba9c4). CiGate.startOfWait now returns a bounded start plus a warning: an unparseable value falls back to now, a future value is clamped to now, and ci-gate, phase-pr and phase-merge print the reason to stderr. An absent value is unchanged and silent. The wait is bounded in every case.

Fixed in PR #501 (merge d1eba9c4). `CiGate.startOfWait` now returns a bounded start plus a warning: an unparseable value falls back to now, a future value is clamped to now, and `ci-gate`, `phase-pr` and `phase-merge` print the reason to stderr. An absent value is unchanged and silent. The wait is bounded in every case.
mph closed this issue 2026-09-02 20:55:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-cli#473
No description provided.