Skip to content

🐛 Fix flaky generate-demos CI job - #2838

Open
pedjak wants to merge 1 commit into
operator-framework:mainfrom
pedjak:fix-demo-flake
Open

🐛 Fix flaky generate-demos CI job#2838
pedjak wants to merge 1 commit into
operator-framework:mainfrom
pedjak:fix-demo-flake

Conversation

@pedjak

@pedjak pedjak commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

The generate-demos CI job fails ~45% of the time (9 of last 20 runs) with jq exit status 5 on the ClusterCatalog Quickstart scenario. The failure is always at the catalog "operatorhubio" contains some packages step.

Three issues contribute to the flakiness:

  1. jq -s (slurp mode) buffers the entire operatorhubio FBC response in memory before processing, risking system errors (exit code 5) on the large operatorhubio catalog
  2. Catalog content queries run exactly once with no retry, so any transient port-forward drop or network hiccup fails the step immediately
  3. bash() does not attach stderr to ExitError, making failures opaque in CI logs

This PR:

  • Removes jq -s (slurp mode) so each JSON object is processed in constant memory via streaming
  • Wraps CatalogContainsSomePackages, PackageHasSomeChannels, and PackageHasSomeBundles in waitFor for retry on transient errors (matching the pattern used by PodHasContainerCount and dozens of other steps)
  • Adds curl --retry 3 --retry-delay 2 --retry-all-errors for transport-level resilience
  • Injects stderr into ExitError in bash() to match the k8sClient diagnostics pattern

Tested locally with make update-demos — all 4 scenarios, 30 steps passed.

Reviewer Checklist

  • API Go Documentation
  • Tests: Unit Tests (and E2E Tests, if appropriate)
  • Comprehensive Commit Messages
  • Links to related GitHub Issue(s)

Copilot AI review requested due to automatic review settings July 31, 2026 16:57
@openshift-ci
openshift-ci Bot requested review from dtfranz and joelanford July 31, 2026 16:58
@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign pedjak for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for olmv1 ready!

Name Link
🔨 Latest commit 4dd08cc
🔍 Latest deploy log https://app.netlify.com/projects/olmv1/deploys/6a6d0d0fad8a810008b26459
😎 Deploy Preview https://deploy-preview-2838--olmv1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@pedjak pedjak changed the title 🐛 Fix flaky generate-demos CI by removing jq slurp mode and adding retry 🐛 Fix flaky generate-demos CI Jul 31, 2026
@pedjak pedjak changed the title 🐛 Fix flaky generate-demos CI 🐛 Fix flaky generate-demos CI job Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The new curl invocation uses -s (silent), which suppresses error output and can negate the intended stderr/diagnostics improvements unless adjusted (e.g., -sS).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR reduces flakiness in the generate-demos E2E/quickstart flow by making catalog content queries more resilient and less resource-intensive when reading large catalogd /api/v1/all JSONL payloads (notably operatorhubio).

Changes:

  • Capture and propagate stderr for bash() command failures by attaching it to exec.ExitError.
  • Switch catalog queries from jq -s slurp mode to streaming jq filters to avoid buffering the full JSONL response in memory.
  • Add curl retries (and wrap catalog query steps in waitFor) to tolerate transient port-forward / network issues.
File summaries
File Description
test/e2e/steps/demo_steps.go Improves robustness of demo E2E steps by enhancing command diagnostics, switching jq processing to streaming, and adding retry behavior around catalog queries.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines 122 to 125
script := fmt.Sprintf(
`curl -s -k https://%s/catalogs/%s/api/v1/all | jq -s '%s'`,
`curl -s -k --retry 3 --retry-delay 2 --retry-all-errors https://%s/catalogs/%s/api/v1/all | jq '%s'`,
addr, catalogName, jqFilter,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — updated to curl -sS so transport/TLS errors are surfaced on stderr while keeping normal output quiet. This pairs well with the new stderr-to-ExitError propagation in bash().

Comment on lines +130 to +137
waitFor(ctx, func() bool {
out, err := catalogCurlJq(ctx, catalogName,
`select(.schema == "olm.package") | .name`)
if err != nil {
return false
}
return strings.TrimSpace(out) != ""
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changed functions will no longer return an error. Should the context be checked for a timeout so that an error can be returned?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The waitFor function uses require.Eventually(godog.T(ctx), ..., timeout, tick) which calls t.FailNow() on timeout — the function never returns on failure, it hard-fails the test via runtime.Goexit(). This matches the established pattern used by PodHasContainerCount in the same file (line 209) and dozens of other step functions throughout the test suite. So from a test perspective, the behavior is: either the condition is met within 5 minutes, or the scenario fails immediately.

Copilot AI review requested due to automatic review settings July 31, 2026 17:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The curl | jq pipeline can still mask curl failures without pipefail, which undermines the intended retry/diagnostics improvements and can reintroduce flakiness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

test/e2e/steps/demo_steps.go:125

  • The curl | jq pipeline is executed under bash -c without pipefail, so a failing curl can be masked if jq exits 0 on empty stdin (common when the connection drops and curl outputs nothing). That defeats the goal of retrying transient transport failures and can make the step fail later with an unhelpful timeout/empty-output symptom instead of surfacing the real error.

Enable pipefail for this command so a non-zero curl exit status propagates to bash()/waitFor and the captured stderr is preserved.

	script := fmt.Sprintf(
		`curl -sS -k --retry 3 --retry-delay 2 --retry-all-errors https://%s/catalogs/%s/api/v1/all | jq '%s'`,
		addr, catalogName, jqFilter,
	)
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@tmshort

tmshort commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Not a good sign when the update-demos is still failing with this.

return "", err
}
script := fmt.Sprintf(
`curl -s -k https://%s/catalogs/%s/api/v1/all | jq -s '%s'`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need jq -s because the catalog format is in JSONLines (jsonl) format, not monolithic JSON, and slurp-mode forces it to re-represent it inside a monolithic object. Without this, jq will error.

@grokspawn

Copy link
Copy Markdown
Contributor

Thanks for looking into this @pedjak!
Looking at the failure logs, it also looks like we still generate a demo 'cast even if the test failed. I haven't investigated to see if the demo is successful in this case, but it casts doubt.

The generate-demos CI job fails ~45% of the time with jq exit status 5
on the ClusterCatalog Quickstart scenario. Three issues contribute:

- jq -s (slurp mode) buffers the entire operatorhubio FBC response in
  memory before processing, risking system errors on large catalogs
- catalog content queries run exactly once with no retry, so any
  transient port-forward or network hiccup fails the step immediately
- bash() does not attach stderr to ExitError, making failures opaque

Remove jq slurp mode so each JSON object is processed in constant
memory. Wrap CatalogContainsSomePackages, PackageHasSomeChannels, and
PackageHasSomeBundles in waitFor for retry on transient errors. Add
curl --retry for transport-level resilience. Inject stderr into
ExitError in bash() to match k8sClient diagnostics.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 21:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The new retry loops currently swallow the underlying curl/jq error details, which can still leave CI failures opaque even with improved stderr propagation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

test/e2e/steps/demo_steps.go:158

  • The retry loop suppresses the concrete catalogCurlJq error by returning false on error; if the command keeps failing, the step will time out with a generic message and no visibility into the last stderr/exit status.

Log the last error (and/or empty output) inside the polling closure so failures are diagnosable in CI output.

	waitFor(ctx, func() bool {
		out, err := catalogCurlJq(ctx, catalogName,
			fmt.Sprintf(`select(.schema == "olm.channel") | select(.package == "%s") | .name`, packageName))
		if err != nil {
			return false

test/e2e/steps/demo_steps.go:170

  • Same as the other catalog queries: returning false on catalogCurlJq errors hides the real failure reason when retries ultimately time out, which makes CI failures hard to debug.

Log the last error/empty output inside the polling closure so the final test output shows what kept failing.

	waitFor(ctx, func() bool {
		out, err := catalogCurlJq(ctx, catalogName,
			fmt.Sprintf(`select(.schema == "olm.bundle") | select(.package == "%s") | .name`, packageName))
		if err != nil {
			return false

test/e2e/steps/demo_steps.go:149

  • The retry loop drops the underlying catalogCurlJq error by just returning false; if the command keeps failing (e.g., curl/jq non-zero) the eventual timeout failure will be generic and won’t include the real cause, undermining the new stderr propagation in bash().

Log the error/output inside the polling closure so Go test output captures the last observed failure when the step times out.

This issue also appears in the following locations of the same file:

  • line 154
  • line 166
	waitFor(ctx, func() bool {
		out, err := catalogCurlJq(ctx, catalogName,
			`select(.schema == "olm.package") | .name`)
		if err != nil {
			return false
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants