ci-gate exits 1 for a used-up --timeout budget, the same code as red CI #719

Open
opened 2026-10-05 18:44:23 +00:00 by claude · 4 comments
Member

Observed behaviour

iw ci-gate exits with code 1 both when CI is red and when the wait used up its --timeout budget (CiVerdict.TimedOut). A caller that acts on the exit code alone cannot tell "a job failed" from "CI did not finish in time". The batch loop in the kanon plugin (iw-plugin/lib/batch/CiGate.scala) reads exit 1 as red and starts fix sessions on CI that never failed (linked issue below).

Code (iw-cli v0.19.0, 0e446674)

  • core/commands/CiGateCommand.scala:70-76 — the declared exit code 1 covers "CI went red, the wait used up its whole --timeout budget, no pull request could be found, or an argument was wrong". The help text tells the caller to raise --timeout for a used-up budget and not retry, but the code alone does not show which case occurred.
  • core/commands/CiGateCommand.scala:251-254 — every decided non-red verdict, TimedOut included, goes to stop(...).
  • core/commands/CiGateCommand.scala:279-294 — stop writes ci_failed, clears the wait (clearCiWait = true), and returns CommandResult.error (1). The only difference from red is the card's headline ("CI timed out" against "CI failed").
  • phase-merge does the same for TimedOut (core/commands/PhaseMerge.scala:278-286, markFailed(..., "CI timed out"), CommandResult.error).

Because the wait start is cleared, the next ci-gate call starts a new full budget. A caller that retries after exit 1 waits again, and does not learn that it already waited the full budget.

Evidence (MEDECA, GitLab gitlab.e-bs.cz CMI/mdr/medeca-modul-poptavky, 2026-10-05, UTC)

MEDECA-656, MR !638 MEDECA-660, MR !652
MR head (review-state commit) pushed b85b279 01:03:22 95d48e5 08:57:50
Head pipeline created 6086, 01:03:34 6121, 08:57:59
Gate stops, card ci_failed "CI timed out" 02:35:25 10:30:05
Head pipeline leaves pending (starts) 02:46:27 10:47:10
Head pipeline success 03:03:43 11:02:59

The head pipelines waited about 1 h 45 min in the runner queue. The gate read pending as running, which is correct, and gave TimedOut after the default 30m budget. Each time it exited 1.

The previous commits' pipelines (6085, 6120) were canceled by GitLab's redundant-pipeline auto-cancel. They are for a different sha than the MR head, and the gate's sha filter (core/model/CiGate.scala:319) drops them, so they did not affect the verdict.

Version

MEDECA runs iw-cli 0.18.0 (./iw --version, version = latest). I read v0.19.0. Between v0.18.0 and v0.19.0 the only gate file that changed is CiGateCommand.scala, and the change (removal of ciGatedSha and the commit policy) does not touch the timeout path or the exit codes.

Suggested fix direction

  • Give "budget used up" its own exit code in ci-gate and phase-merge (for example 4 or 5; 3 is already "unfinished, call again"), and declare it in the command's ExitCode list with the caller instruction "raise --timeout; do not try to fix code".
  • Optionally also show the verdict kind in the card in a field that is easy to read by machine (not only in the display headline), so a caller that reads the card can tell the two cases apart.

Related: kanon plugin issue for the batch loop (linked in a comment).

🤖 Generated with Claude Code

## Observed behaviour `iw ci-gate` exits with code 1 both when CI is red and when the wait used up its `--timeout` budget (`CiVerdict.TimedOut`). A caller that acts on the exit code alone cannot tell "a job failed" from "CI did not finish in time". The batch loop in the kanon plugin (`iw-plugin/lib/batch/CiGate.scala`) reads exit 1 as red and starts fix sessions on CI that never failed (linked issue below). ## Code (iw-cli v0.19.0, `0e446674`) - `core/commands/CiGateCommand.scala:70-76` — the declared exit code 1 covers "CI went red, the wait used up its whole --timeout budget, no pull request could be found, or an argument was wrong". The help text tells the caller to raise `--timeout` for a used-up budget and not retry, but the code alone does not show which case occurred. - `core/commands/CiGateCommand.scala:251-254` — every decided non-red verdict, `TimedOut` included, goes to `stop(...)`. - `core/commands/CiGateCommand.scala:279-294` — `stop` writes `ci_failed`, clears the wait (`clearCiWait = true`), and returns `CommandResult.error` (1). The only difference from red is the card's headline ("CI timed out" against "CI failed"). - `phase-merge` does the same for `TimedOut` (`core/commands/PhaseMerge.scala:278-286`, `markFailed(..., "CI timed out")`, `CommandResult.error`). Because the wait start is cleared, the next `ci-gate` call starts a new full budget. A caller that retries after exit 1 waits again, and does not learn that it already waited the full budget. ## Evidence (MEDECA, GitLab gitlab.e-bs.cz CMI/mdr/medeca-modul-poptavky, 2026-10-05, UTC) | | MEDECA-656, MR !638 | MEDECA-660, MR !652 | |---|---|---| | MR head (review-state commit) pushed | `b85b279` 01:03:22 | `95d48e5` 08:57:50 | | Head pipeline created | 6086, 01:03:34 | 6121, 08:57:59 | | Gate stops, card `ci_failed` "CI timed out" | 02:35:25 | 10:30:05 | | Head pipeline leaves `pending` (starts) | 02:46:27 | 10:47:10 | | Head pipeline `success` | 03:03:43 | 11:02:59 | The head pipelines waited about 1 h 45 min in the runner queue. The gate read `pending` as running, which is correct, and gave `TimedOut` after the default 30m budget. Each time it exited 1. The previous commits' pipelines (6085, 6120) were `canceled` by GitLab's redundant-pipeline auto-cancel. They are for a different sha than the MR head, and the gate's sha filter (`core/model/CiGate.scala:319`) drops them, so they did not affect the verdict. ## Version MEDECA runs iw-cli **0.18.0** (`./iw --version`, `version = latest`). I read **v0.19.0**. Between v0.18.0 and v0.19.0 the only gate file that changed is `CiGateCommand.scala`, and the change (removal of `ciGatedSha` and the commit policy) does not touch the timeout path or the exit codes. ## Suggested fix direction - Give "budget used up" its own exit code in `ci-gate` and `phase-merge` (for example 4 or 5; 3 is already "unfinished, call again"), and declare it in the command's `ExitCode` list with the caller instruction "raise --timeout; do not try to fix code". - Optionally also show the verdict kind in the card in a field that is easy to read by machine (not only in the display headline), so a caller that reads the card can tell the two cases apart. Related: kanon plugin issue for the batch loop (linked in a comment). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Member

Related kanon plugin issue (the batch loop that reads this exit code as red): iterative-works/dev-docs#458

🤖 Generated with Claude Code

Related kanon plugin issue (the batch loop that reads this exit code as red): https://code.iterative.works/iterative-works/dev-docs/issues/458 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Member

Intake: agent-ready
Workflow: diagnostic
Reason: all gates passed
Analysis: project-management/issues/IW-719/analysis.md on branch IW-719

Intake: agent-ready Workflow: diagnostic Reason: all gates passed Analysis: project-management/issues/IW-719/analysis.md on branch IW-719
Author
Member

Implement: needs-human
Reason: batch-implement exit 1 — the kanon plugin command did not compile against the iw-cli core of the current main, so no phase ran. The installed plugin (lib/batch/ForgeCatchUp.scala) expects Either[String, …] from fetchPullRequestForBranch; main now returns Either[TrackerError, …] (8e902777, "TrackerError on the forge clients and the port's PR/CI/merge calls"). No batch-implement.log was written; these are the last lines of the command output:

[error] /home/mph/.local/share/iw/plugins/kanon/lib/batch/ForgeCatchUp.scala:143:7
[error] Found:    Either[iw.core.model.TrackerError, Option[batch.ForgeState]]
[error] Required: Either[String, Option[batch.ForgeState]]
Compilation failed
exit=1

The cause is not in this issue: the kanon plugin must be updated for the TrackerError change. After that, release the issue to agent-ready again through human-review.md.

Implement: needs-human Reason: batch-implement exit 1 — the kanon plugin command did not compile against the iw-cli core of the current main, so no phase ran. The installed plugin (`lib/batch/ForgeCatchUp.scala`) expects `Either[String, …]` from `fetchPullRequestForBranch`; main now returns `Either[TrackerError, …]` (8e902777, "TrackerError on the forge clients and the port's PR/CI/merge calls"). No `batch-implement.log` was written; these are the last lines of the command output: ``` [error] /home/mph/.local/share/iw/plugins/kanon/lib/batch/ForgeCatchUp.scala:143:7 [error] Found: Either[iw.core.model.TrackerError, Option[batch.ForgeState]] [error] Required: Either[String, Option[batch.ForgeState]] Compilation failed exit=1 ``` The cause is not in this issue: the kanon plugin must be updated for the TrackerError change. After that, release the issue to `agent-ready` again through human-review.md.
Author
Member

Release: the implement loop failed this issue for a reason outside it. The kanon plugin did not compile against iw-cli main, because the tracker port now returns TrackerError (iw-cli #778). The plugin fix is dev-docs #484, and iw-cli 0.22.1 carries the core change. The issue goes back to the implement loop.

Release: the implement loop failed this issue for a reason outside it. The kanon plugin did not compile against iw-cli main, because the tracker port now returns TrackerError (iw-cli #778). The plugin fix is dev-docs #484, and iw-cli 0.22.1 carries the core change. The issue goes back to the implement loop.
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#719
No description provided.