ci(#6697): make harness-wait timeouts report why the wait failed - #6698
Conversation
|
🤖 Finished Review · ✅ Success · Started 8:19 PM UTC · Completed 8:35 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.12 |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Risk Assessment: moderate (2/5) DetailsScore holds at 2 (moderate). Focused 2-file bug fix with 50% test coverage ratio in a high-churn area (12 commits/30d, 7 fix commits/90d), scoped tightly to harness-wait timeout diagnostics with bot authorship and no protected/security paths touched. Previous runRisk Assessment: moderate (2/5) DetailsFocused 2-file bug fix with 50% test coverage ratio in a high-churn area (12 commits/30d, 9 fix commits/90d), but scoped tightly to the harness-wait deadline with bot authorship and no protected/security paths touched. |
ReviewFindingsLow
Previous runReviewFindingsMedium
|
08344ae to
ffbd823
Compare
|
🤖 Review · Commit: |
ffbd823 to
87f9337
Compare
|
🤖 Review · Commit: |
87f9337 to
24a4b34
Compare
|
🤖 Review · Commit: |
24a4b34 to
a91248a
Compare
|
🤖 Review · Commit: |
a91248a to
4ceb343
Compare
|
🤖 Review · Commit: |
Investigating the #6647 behaviour-test failures showed both were misreported by the harness-wait timeout path, not caused by the deadline or the artifact lookup: - Attempt 1: the harness runs existed within 2s of the label, but the pool-org installation token was rate limited (403) for the rest of the wait. listHarnessRunsAfter turned every listing error into an empty list, so the timeout said "no recent workflow runs found". Client-side retries also stretched the 12-minute wait to 26 minutes. - Attempt 2: dispatch declined the event (collaborator permission lookup failed for the labelling actor on a freshly recreated pool repo), the harness matrix never expanded, and no artifact was ever uploaded. The timeout showed a "successful" run with no explanation. Record listing errors during the wait instead of swallowing them, bound each poll's API calls to the remaining wait budget, and make the timeout diagnostics state the agent job's state per run (detecting the unexpanded matrix), classify listed artifacts against the trigger time, and report the recorded errors. Add a nowFunc seam so the timeout branch is unit-testable. The 15-minute harnessWait and run-first fallback from the first cut of this branch are dropped: neither addressed an observed failure, and the fallback added an API call per run per poll under rate pressure. Closes #6697 Assisted-by: Claude (fix), Codex (review), Gemini (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
4ceb343 to
3f4d92f
Compare
|
🤖 Finished Review · ✅ Success · Started 9:33 PM UTC · Completed 9:50 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.42 |
waynesun09
left a comment
There was a problem hiding this comment.
Code-owner approval after five multi-agent review rounds (Claude, Codex, Gemini) plus the review bot's approval at 3f4d92f. Every MEDIUM+ finding was fixed and re-verified: run-vs-job conclusion (moot, code removed), zero-budget final poll, diagnostics-budget cutoff headline, cutoff classification tied to the context's own deadline, placeholder-job attribution through the Harness dispatch job, non-exhaustive cause wording. go test -race -count=5 clean; both real #6647 failure shapes render correctly against the live client source. Diagnostics-only change; dispatchWait and the #6669 backoff untouched.
|
🤖 Finished Retro · ✅ Success · Started 12:04 AM UTC · Completed 12:24 AM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.42 |
Retro: PR #6698 — make harness-wait timeouts report why the wait failedTimeline
What went well
What could improve
Evidence for existing issues
Proposals filed |
The harness-wait poll loop (and its diagnostics) re-request the same workflow-runs/jobs/artifacts URLs every few seconds, from up to a dozen concurrent scenarios sharing one installation token. #6705's instrumentation measured that traffic draining the primary quota ~235 req/min, exhausting it ~20 minutes into a suite run. GitHub does not count a 304 response against the primary rate-limit budget (verified against the live API: repeated If-None-Match requests left X-RateLimit-Remaining unchanged, only the initial uncached GET consumed one unit). This adds a small conditional-GET cache to LiveClient (etagCache, opt-in per path via getCached) and wires it into the five GET endpoints the harness-wait poll loop and its diagnostics use: ListWorkflowRuns, ListRecentWorkflowRuns, ListWorkflowRunJobs, ListWorkflowRunArtifacts, and ListRepositoryArtifacts. Unchanged results between polls now cost nothing; a status change still forces a full re-fetch, since GitHub issues a new ETag whenever the underlying data changes. do() grows a variadic requestHeader option so getConditional can set If-None-Match without touching its other 28 call sites. The cache is capped (etagCacheLimit) since a long suite run touches many distinct run/job/artifact URLs. Deliberately out of scope: a remaining-budget circuit breaker (the issue's second candidate). If post-merge behaviour runs still show #6698's 403 diagnostics after this lands, that's the next step, sized with real data instead of a guessed threshold. Assisted-by: Claude Signed-off-by: Wayne Sun <gsun@redhat.com>
Summary
Makes the harness-wait timeout errors in the E2E behaviour-test GitHub Actions driver report what actually went wrong. Investigating the two #6647 failures that motivated #6697 showed both were misreported by this path rather than caused by the deadline or the artifact lookup, so this PR replaces the first cut (15-minute
harnessWait+ run-first success fallback) with error surfacing and diagnostics.Related Issue
Closes #6697
What the #6647 failures actually were
Both from E2E run 33104621218:
no recent workflow runs found after trigger time): thefullsend.yamlruns existed — created 2s after the label inhalfsend-10/test-repo-11andtest-repo-08, both concludedsuccess. The pool-org installation token was rate limited (403 retryable error after 5 attempts ... last delay: 54sin the same job's log from 18:54 onward).listHarnessRunsAfterturned every listing error into an empty list and the artifact branch wasif err == nil, so the wait was blind for its whole window and reported that nothing existed. Client-side retries (5 × Retry-After) also stretched the 12-minute wait to ~26 minutes.recent workflow runs (1): run 33108146650: status=completed conclusion=success): that run has 0 artifacts and itsHarness run (${{ matrix.agent }})job isskipped— the matrix never expanded. The dispatch job loggedcollaborator permission lookup failed for fullsend-ai-e2e[bot] on halfsend-11/test-repo-01: not foundon a repo recreated minutes earlier, and emittedmatrix={"include":[]}. The harness was never dispatched; no wait length or artifact lookup would have found it.Neither cause is fixed here (see follow-ups below) — but the timeout now names them.
Changes
listHarnessRunsAfterreturns its error instead of an empty list; both harness waits record artifact/run listing failures per poll (pollErrors) and keep polling — a transient error never ends a scenario.dispatchWait. A poll is skipped rather than started with less thanpollMinBudget(5s), and a call cut short by the budget is counted separately from API failures — it is never presented as one.harnessTimeoutDiagnostics, a 2-minute envelope with a 30slookupBudgetper call since the token may still be rate limited; both single-call listings run before the per-run job lookups so neither can be starved; a lookup cut short at diagnostics time — judged by its own context, not the error type — headlines the last poll error instead of its own cutoff):Harness dispatchjob's own conclusion (placeholderskipped+ dispatch succeeded → empty matrix, i.e. dispatch declined the event; dispatch failed → that job's failure; placeholdercancelled→ the run ended before the matrix was evaluated, which says nothing about the matrix) (attempt 2's shape);run listing failed: <err> (same error on N of M polls during the wait)when the wait was blind (attempt 1's shape), plus run-lookup (GetWorkflowRun/ jobs) failures recorded during the wait.nowFuncseam onDriver(defaulttime.Now) so the timeout branch is unit-testable;dispatchWaitis unchanged.harnessWait = 15m(no latency data supported it; under rate limiting it only extends blind polling) and the run-first success fallback (addressed no observed failure, checked run rather than job conclusion, and added aListWorkflowRunJobscall per run per poll — more API pressure in exactly the failing condition).Testing
go vet,gofmtclean;go test -race ./pkg/behaviourtest/...passesTestWaitForHarnessAgent_TimeoutReportsListingErrors/TestWaitForFailedHarnessAgent_TimeoutReportsListingErrors— every listing fails (403); timeout names the error and the poll count, never "no recent workflow runs"TestWaitForHarnessAgent_TimeoutReportsUnexpandedMatrix— successful run with skipped placeholder job → "harness matrix not expanded ... dispatch declined the event"TestWaitForHarnessAgent_TimeoutReportsAgentJobAndArtifactRejection— agent job state per run + artifact rejected against the trigger timeTestWaitForHarnessAgent_PollsBoundedByRemainingBudget— every poll call carries a deadline within[pollMinBudget, dispatchWait], shrinking per iteration; diagnostics lookups withinlookupBudgetTestHarnessTimeoutDiagnostics_CutShortVsLiveDeadlineError— a diagnostics lookup cut short by its budget headlines the recorded 403 (or names the cutoff when nothing was recorded); a deadline error under a live context is reported as the real failure it isTestWaitForHarnessAgent_SkipsPollBelowMinimumBudget— a poll that would start with <5s is skipped, not run and reported as cut shortTestFormatRunDiagnosticsWithJobs— empty and multi-run headersTestDescribeAgentJob— agent job / unexpanded matrix (dispatch succeeded, failed, absent; placeholder cancelled rather than skipped) / no jobs / lookup errorTestPollErrors_KeepsInformativeErrorOverBudgetCutoff(budget cutoff vs a deadline error under a live or merely cancelled context, jitter-insensitive same-error collapse),TestFormatArtifactDiagnosticsFollow-ups (not in this PR)
Checklist
!for breaking changes)WaitForWorkflow's dispatch-detection window or the backoff constants from ci(#6668): add exponential backoff to dispatch polling #6669