phase-pr cannot tell a forge that is down from a forge that refused, so both stop a batch #513
Labels
No labels
agent-ready
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
needs-human
needs-input
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli#513
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
phase-pr's duplicate path refuses with exit 1 on three conditions that are not alike:The third is
Left, notRight(Some(resolved)). Nothing is resolved and nothing contradicts, yet an unattended batch stops on it, while the CI wait treats an equivalent "cannot say yet" as exit 3 and polls. The asymmetry is real and was raised by the error-handling review and independently by the plugin consumer.Why exit 3 is not the fix as things stand
Checked before answering, and both checks fail.
There is no budget at that point in the command.
ci_wait_startedis written inside the gate (core/commands/CiGate.scala:249). The duplicate path runs inresolvePullRequest, whichcreatePrAndGatecalls beforegateAndFinish(core/commands/PhasePr.scala:115-124). So when this refusal happens, no wait has started and no budget exists.PhaseOutcome.AwaitCIdeliberately does not consume the recovery budget —core/model/BatchImplement.scala:11-15says the wait must not be paid for out of it — so an exit 3 here would be bounded by nothing. Against a forge that is down, the batch would re-dispatch forever, and each iteration re-runsenv.git.push(PhasePr.scala:115) before reaching the refusal again.Leftis not reliably transient. It carries permanent configuration failures alongside network ones:GitHubClient.requireGhreturns "gh not installed" and "gh not authenticated" (GitHubClient.scala:1003-1015)GitLabClient.requireGlabdoes the same for glabSplitting on
Leftalone would put a missing binary or a bad token into an unbounded retry, which is a worse failure than stopping: the batch never ends and never says why.What the fix actually needs
Both, and in this order:
Either[String, A], so the only thing a caller can do is match on message text. The error-handling review named this as the one place the codebase's stringly-typed errors cost something real. A small error type on the tracker port — enough to separate "could not reach the forge" from "the forge refused you" — is the prerequisite.Only then does exit 3 become the right answer for case 3, and it needs no fourth exit code: the 0/1/3 contract already means "nothing failed, run this again".
Until then
Exit 1 stands, and the trade-off is recorded in
pullRequestOnBranch's own docstring. A batch stopped on a transient blip is recoverable by a person; a batch that loops forever, or one that merges against a stale reading, is not.Origin
Raised by the error-handling review of PR #511 and refined by the kanon session, which noticed it had asked for exit 1 here while mapping the same condition to "resume normal flow" in its own
catchUpMergedPhases.Intake: needs-human
Workflow: waterfall
Reason: G1 — one CLARIFY marker (analysis.md:260): the analysis refuses the typed-error-plus-persisted-budget scope the issue states and proposes a bounded in-process retry instead; a human picks the scope
Analysis: project-management/issues/IW-513/analysis.md on branch IW-513
Intake: needs-input
Every technical claim in this issue verifies (line numbers have drifted, the facts hold):
ci_wait_startedis written inmarkPendingatCiGate.scala:264;resolvePullRequestruns beforegateAndFinishatPhasePr.scala:217-226withenv.git.pushat :221;BatchImplement.scala:13-14says "the recovery budget must not pay for the wait"; andrequireGh(GitHubClient.scala:982-994) /requireGlab(GitLabClient.scala:1010-1022) return "not installed" and "not authenticated" through the sameLeftchannel. The docstring claim is true too —pullRequestOnBranch(PhasePr.scala:286-305) states every refusal there is terminal, andPhasePrHarnessTest.scala:889pins it.The asymmetry is real today: commit
984c5c0bon main made a silent forge in the gate returnCiVerdict.Unreadable, whichPhasePr.scala:428-440turns into exit 3. (An earlier analysis on an unmerged branch denied this premise; that denial is now out of date.)The blocker is size, not doubt. Typing
fetchPullRequestForBranchalone touches 13 files (CommandEnv,LiveCommandEnv,ForgeGateway, three clients,PhasePr, dashboardPullRequestReader,FakeCommandEnv, four test files); part 2 adds areview-state.jsonfield plusReviewStateUpdater,BatchImplementand the 0/1/3 contract docs. Honest total: 4 phases, above the 3-phase ceiling the unattended loop will run.1. This issue specifies a two-part fix (typed transient/permanent error on the tracker port, then a pre-gate budget). A narrower alternative exists: keep
Either[String, A]and give the duplicate path the same bounded in-process retry the CI gate already uses (~3 attempts, 30s apart, then exit 1). Which do you want built — the narrow retry (~1 phase), the full two-part fix (~4 phases, needing a split into separate issues), or the narrow retry now with the typed error deferred until a real outage proves 90 seconds too thin?2. Typing the error on
fetchPullRequestForBranchchanges a signature in the publishedcorejar, which the dashboard (PullRequestReader) and the kanon plugin both link against. Is breaking that published signature acceptable here, or must the typed error be added without changing existing callers?3. Should the typed error cover only
fetchPullRequestForBranch, or every forge call that currently returnsEither[String, A](~84 such returns across the three clients)? The error-handling review this issue cites asked for the general case; the issue text asks for "a small error type ... enough to separate".Not a duplicate: #485 (no-pipeline-yet passes the gate) and #491 (push after reading CI) are different
phase-prdefects, and #400 is about the batch loop rather than the pre-gate path.Note: an earlier intake reached the same conclusion on 2026-09-03 (commit
b96f2048, "intake analysis — needs-human") via a CLARIFY marker. That commit is not on main and no branch contains it, so its analysis is orphaned; the question it raised was never answered.Intake: agent-ready
Workflow: waterfall
Reason: all gates passed
Analysis: project-management/issues/IW-513/analysis.md on branch IW-513