Skip to content

NO-ISSUE: Fix NS_BINDING_ABORTED noise and lifecycle cache signal coupling#16773

Open
perdasilva wants to merge 5 commits into
openshift:mainfrom
perdasilva:NO-ISSUE-lifecycle-abort-error-fix
Open

NO-ISSUE: Fix NS_BINDING_ABORTED noise and lifecycle cache signal coupling#16773
perdasilva wants to merge 5 commits into
openshift:mainfrom
perdasilva:NO-ISSUE-lifecycle-abort-error-fix

Conversation

@perdasilva

@perdasilva perdasilva commented Jul 17, 2026

Copy link
Copy Markdown

Analysis / Root cause:
When useOperatorLifecycle re-runs (e.g. subscription loads after CSV, or
multiple components share the same cache key), fetchLifecycleData returns the
cached in-flight promise tied to the previous caller's AbortController signal.
When that old signal is aborted, the AbortError propagates to the new caller
whose signal is still live — three problems result:

  1. coFetch calls console.warn for every abort, flooding Firefox devtools
    with NS_BINDING_ABORTED noise on the Installed Operators page.
  2. The hook's .catch only checked !controller.signal.aborted (the new
    controller), not whether the error was an AbortError, so it incorrectly
    surfaced the error in UI state.
  3. When a cached promise was aborted by another caller, the waiting component
    stalled in an empty non-loading state instead of retrying.

Solution description:

  • console-fetch.ts: Don't console.warn for DOMException AbortErrors —
    they are expected cancellations from AbortController.abort(), not failures.
  • useOperatorLifecycle.ts (.catch guard): Skip setError when
    err.name === 'AbortError' regardless of which controller triggered it;
    still call setLoading(false) so the hook settles cleanly.
  • useOperatorLifecycle.ts (fetchLifecycleData): When returning a cached
    in-flight promise, chain .catch() that detects if the original was aborted
    by another caller while our signal is still live, deletes the stale cache
    entry, and issues a fresh retry rather than leaving the component stalled.

Screenshots / screen recording:
N/A — no visual change; eliminates console noise and fixes error-state regression on the Installed Operators page when lifecycle metadata is enabled.

Test setup:
Tested locally against a TechPreview cluster (TechPreviewNoUpgrade feature set) with --olm-lifecycle-metadata=true.

Test cases:

  • coFetch does not console.warn when fetch is aborted via AbortController (new)
  • coFetch does console.warn for real network errors (new)
  • useOperatorLifecycle does not surface AbortError as error state (new)
  • useOperatorLifecycle retries when cached in-flight promise is aborted by another caller (new)
  • useOperatorLifecycle surfaces real network errors correctly (new)
  • useOperatorLifecycle skips fetch when parameters are missing (new)

Browser conformance: N/A

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved cancellation handling: aborted requests no longer trigger warning logs or error state updates.
    • Lifecycle loading/error states are now more consistent, with clearer behavior across concurrent callers.
    • Concurrent lifecycle requests are deduplicated and cached so repeated mounts don’t re-fetch unnecessarily.
    • Network requests are skipped when required package information is missing.
  • Tests
    • Added Jest coverage for cancellation, non-cancellation error handling, caching, deduplication, and missing package scenarios in fetch and lifecycle hook behavior.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 17, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@perdasilva: This pull request explicitly references no jira issue.

Details

In response to this:

Analysis / Root cause:
When useOperatorLifecycle re-runs (e.g. subscription loads after CSV, or
multiple components share the same cache key), fetchLifecycleData returns the
cached in-flight promise tied to the previous caller's AbortController signal.
When that old signal is aborted, the AbortError propagates to the new caller
whose signal is still live — three problems result:

  1. coFetch calls console.warn for every abort, flooding Firefox devtools
    with NS_BINDING_ABORTED noise on the Installed Operators page.
  2. The hook's .catch only checked !controller.signal.aborted (the new
    controller), not whether the error was an AbortError, so it incorrectly
    surfaced the error in UI state.
  3. When a cached promise was aborted by another caller, the waiting component
    stalled in an empty non-loading state instead of retrying.

Solution description:

  • console-fetch.ts: Don't console.warn for DOMException AbortErrors —
    they are expected cancellations from AbortController.abort(), not failures.
  • useOperatorLifecycle.ts (.catch guard): Skip setError when
    err.name === 'AbortError' regardless of which controller triggered it;
    still call setLoading(false) so the hook settles cleanly.
  • useOperatorLifecycle.ts (fetchLifecycleData): When returning a cached
    in-flight promise, chain .catch() that detects if the original was aborted
    by another caller while our signal is still live, deletes the stale cache
    entry, and issues a fresh retry rather than leaving the component stalled.

Screenshots / screen recording:
N/A — no visual change; eliminates console noise and fixes error-state regression on the Installed Operators page when lifecycle metadata is enabled.

Test setup:
Tested locally against a TechPreview cluster (TechPreviewNoUpgrade feature set) with --olm-lifecycle-metadata=true.

Test cases:

  • coFetch does not console.warn when fetch is aborted via AbortController (new)
  • coFetch does console.warn for real network errors (new)
  • useOperatorLifecycle does not surface AbortError as error state (new)
  • useOperatorLifecycle retries when cached in-flight promise is aborted by another caller (new)
  • useOperatorLifecycle surfaces real network errors correctly (new)
  • useOperatorLifecycle skips fetch when parameters are missing (new)

Browser conformance: N/A

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested review from rhamilto and spadgett July 17, 2026 11:45
@openshift-ci openshift-ci Bot added component/olm Related to OLM component/shared Related to console-shared labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Changes

Abort handling and lifecycle caching

Layer / File(s) Summary
Abort-aware console fetch handling
frontend/packages/console-shared/src/utils/console-fetch.ts, frontend/packages/console-shared/src/utils/__tests__/console-fetch.spec.ts
coFetch rethrows AbortError without logging, while other failures retain warning and rethrow behavior covered by tests.
Lifecycle request caching and hook state
frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts, frontend/packages/operator-lifecycle-manager/src/hooks/__tests__/useOperatorLifecycle.spec.ts
useOperatorLifecycle deduplicates in-flight requests, serves cached data, removes abort cleanup, and tests success, errors, missing package names, deduplication, and cache hits.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: rhamilto, spadgett

🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is clear and relevant, but it is not prefixed with a Jira issue key as required by the repository template. Prefix the title with a Jira key, for example, "CONSOLE-XXXX: Fix NS_BINDING_ABORTED noise and lifecycle cache signal coupling".
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (13 passed)
Check name Status Explanation
Description check ✅ Passed Most required sections are filled with concrete details, including root cause, solution, tests, and setup, so the description is mostly complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All added test titles are static strings; no dynamic IDs, timestamps, or generated names appear in titles.
Test Structure And Quality ✅ Passed Not applicable: the PR adds Jest/RTL unit tests, not Ginkgo; they use beforeEach and no Eventually/Consistently or cluster resources.
Microshift Test Compatibility ✅ Passed Only Jest/RTL unit tests changed; no new Ginkgo e2e specs or MicroShift-unsupported OpenShift APIs were added.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests were added; the new tests are Jest unit tests with no multi-node or HA assumptions, so the SNO check is not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed Only frontend fetch/hook logic and tests changed; no manifests, controllers, affinity, selectors, replicas, or PDBs were added.
Ote Binary Stdout Contract ✅ Passed PR only touches frontend utils/hooks/tests; no main/TestMain/RunSpecs or top-level stdout writes were added, and the only console.warn is in ordinary error handling.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the changes are Jest unit tests and hook code, with no IPv4-only assumptions or external connectivity.
No-Weak-Crypto ✅ Passed Touched files only adjust fetch/error handling and cache logic; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB or custom crypto, and no secret comparisons found.
Container-Privileges ✅ Passed Diff only changes two TypeScript hook/test files; no K8s manifests or privileged fields (privileged, hostPID/Network/IPC, SYS_ADMIN, allowPrivilegeEscalation) were added.
No-Sensitive-Data-In-Logs ✅ Passed No new sensitive logging found; the only warn path suppresses AbortError noise and logs a request path plus error, with no secrets, PII, or session IDs introduced.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Per G. da Silva and others added 3 commits July 17, 2026 13:51
When useOperatorLifecycle re-runs (deps changed) while a fetch is
in-flight, fetchLifecycleData returns the existing cached promise
(which was created with the previous AbortController's signal). When
the old controller is then aborted, that promise rejects with
AbortError. The .catch handler only checked !controller.signal.aborted
(the NEW controller, which is not aborted), so the AbortError was
incorrectly surfaced as a real error, producing NS_BINDING_ERROR
noise in the browser console.

Fix: also skip setError when err.name === 'AbortError', while still
calling setLoading(false) so the loading state settles correctly.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Fix A — console-fetch.ts:
  Don't console.warn for AbortErrors. Aborted fetches are expected when
  AbortController.abort() is called (component unmount, dep change), not
  failures. The warn produced NS_BINDING_ABORTED noise in Firefox devtools.

Fix B — useOperatorLifecycle.ts:
  When fetchLifecycleData returns a cached in-flight promise tied to a
  different caller's signal, chain a .catch() that detects if the original
  was aborted by someone else while our signal is still live and retries
  with a fresh request. Previously, the spurious AbortError would propagate
  to the hook's .catch and leave the row in an empty non-loading state.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@perdasilva
perdasilva force-pushed the NO-ISSUE-lifecycle-abort-error-fix branch from f74b63a to 4068438 Compare July 17, 2026 11:52
@openshift-ci

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: perdasilva
Once this PR has been reviewed and has the lgtm label, please assign rawagner 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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test-prow-playwright-e2e.sh (1)

10-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Usage comment doesn't list the new feature-gate scenario.

The script now accepts feature-gate as a scenario (line 62), but the usage comment (line 10) still says [e2e|release|smoke] and the scenario list (lines 13-14) omits it.

📝 Proposed fix
 #   ./test-prow-playwright-e2e.sh [e2e|release|smoke|feature-gate] [arguments passed to: playwright test ...]
 #
 # Scenarios (first argument; default: e2e):
 #   e2e, release  — full Playwright suite (default project / config)
 #   smoke         — Playwright smoke project (--project=smoke)
+#   feature-gate  — Playwright tests tagged `@feature-gate` (--grep `@feature-gate`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-prow-playwright-e2e.sh` around lines 10 - 14, Update the usage
documentation for test-prow-playwright-e2e.sh to include feature-gate in the
accepted scenario list and describe its behavior alongside e2e, release, and
smoke, matching the scenario handling implemented in the script.
🧹 Nitpick comments (2)
test-prow-e2e-techpreview.sh (1)

18-20: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider adding ARTIFACT_DIR validation for consistency with the Playwright techpreview script.

The Playwright techpreview script (test-prow-playwright-e2e-techpreview.sh, lines 22-30) validates that ARTIFACT_DIR is set, absolute, and not /. This script lacks that validation. With set -u, an unset ARTIFACT_DIR fails cryptically at line 20; an empty or relative value would silently produce a broken INSTALLER_DIR.

🛡️ Proposed fix
 set -exuo pipefail
 
+ARTIFACT_DIR=${ARTIFACT_DIR:-/tmp/artifacts}
 INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer}
+
+if [ -z "$ARTIFACT_DIR" ]; then
+  echo "Error: ARTIFACT_DIR is not set" >&2
+  exit 1
+fi
+case "$ARTIFACT_DIR" in
+  /) echo "Error: ARTIFACT_DIR must not be '/'" >&2; exit 1 ;;
+  /*) ;;
+  *) echo "Error: ARTIFACT_DIR must be an absolute path, got: $ARTIFACT_DIR" >&2; exit 1 ;;
+esac
+export ARTIFACT_DIR
+mkdir -p "${ARTIFACT_DIR}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test-prow-e2e-techpreview.sh` around lines 18 - 20, Add validation
immediately after set -exuo pipefail in the techpreview script to ensure
ARTIFACT_DIR is set, non-empty, absolute, and not /. Keep the existing
INSTALLER_DIR defaulting behavior, but make it run only after validation so
unset, empty, or relative values fail with a clear error.
frontend/integration-tests/tsconfig.json (1)

5-5: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Drop the Cypress path from include
frontend/integration-tests/tsconfig.json can keep types: ["cypress"]; the ../node_modules/cypress glob only widens the set of files TypeScript scans.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/integration-tests/tsconfig.json` at line 5, Update the include
configuration in frontend/integration-tests/tsconfig.json to remove the
../node_modules/cypress entry, leaving only the integration test TypeScript
glob. Preserve the existing types configuration for Cypress.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md`:
- Around line 75-79: Update the spec’s techPreviewOnly fixture description to
match the implementation in the techPreviewOnly fixture: query the FeatureGate
custom resource through k8sClient.getClusterCustomResource and skip unless
spec.featureSet equals TechPreviewNoUpgrade, removing the page.evaluate and
window.SERVER_FLAGS.techPreview behavior.

In `@frontend/integration-tests/support/tech-preview-guard.ts`:
- Around line 26-27: Update requireFeatureGate in tech-preview-guard.ts so it
evaluates enabled gates only from the FeatureGate status entry matching
spec.featureSet, rather than aggregating all feature sets. Fetch the full
FeatureGate resource and filter its parsed JSON in JavaScript, matching the
active-feature-set handling in the Playwright requireFeatureGate fixture.
- Around line 14-22: Remove the top-level before() side effect in
tech-preview-guard.ts and export the guard as a named function such as
requireTechPreview(). Keep its existing featureSet check and skip behavior, and
require callers to explicitly invoke it when they need the TechPreview-only
guard while allowing requireFeatureGate imports to run on graduated or otherwise
enabled clusters.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`:
- Around line 54-57: Update the AbortError recovery in fetchLifecycleData to
delete cacheKey only if the cache still references the rejected promise being
handled, preserving a retry installed by another caller; otherwise reuse the
existing retry. Add a regression test covering three callers sharing the aborted
promise and verifying only the first retry is retained.

---

Outside diff comments:
In `@test-prow-playwright-e2e.sh`:
- Around line 10-14: Update the usage documentation for
test-prow-playwright-e2e.sh to include feature-gate in the accepted scenario
list and describe its behavior alongside e2e, release, and smoke, matching the
scenario handling implemented in the script.

---

Nitpick comments:
In `@frontend/integration-tests/tsconfig.json`:
- Line 5: Update the include configuration in
frontend/integration-tests/tsconfig.json to remove the ../node_modules/cypress
entry, leaving only the integration test TypeScript glob. Preserve the existing
types configuration for Cypress.

In `@test-prow-e2e-techpreview.sh`:
- Around line 18-20: Add validation immediately after set -exuo pipefail in the
techpreview script to ensure ARTIFACT_DIR is set, non-empty, absolute, and not
/. Keep the existing INSTALLER_DIR defaulting behavior, but make it run only
after validation so unset, empty, or relative values fail with a clear error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: affb4291-ecb3-465d-a419-2f0563bb14dc

📥 Commits

Reviewing files that changed from the base of the PR and between 41d3938 and f74b63a.

📒 Files selected for processing (16)
  • .gitignore
  • docs/superpowers/plans/2026-07-16-techpreview-e2e-test-filtering.md
  • docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md
  • frontend/.eslintignore
  • frontend/e2e/fixtures/index.ts
  • frontend/e2e/tests/olm/olmv1-catalog.spec.ts
  • frontend/integration-tests/.eslintrc.cjs
  • frontend/integration-tests/support/tech-preview-guard.ts
  • frontend/integration-tests/tsconfig.json
  • frontend/packages/console-shared/src/utils/__tests__/console-fetch.spec.ts
  • frontend/packages/console-shared/src/utils/console-fetch.ts
  • frontend/packages/operator-lifecycle-manager/src/hooks/__tests__/useOperatorLifecycle.spec.ts
  • frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts
  • test-prow-e2e-techpreview.sh
  • test-prow-playwright-e2e-techpreview.sh
  • test-prow-playwright-e2e.sh

Comment thread docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md Outdated
Comment thread frontend/integration-tests/support/tech-preview-guard.ts Outdated
Comment thread frontend/integration-tests/support/tech-preview-guard.ts Outdated
Comment thread frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts Outdated
@perdasilva

Copy link
Copy Markdown
Author

/retest

Per G. da Silva and others added 2 commits July 17, 2026 14:17
When N callers share the same aborted cached promise, the previous fix
unconditionally deleted the cache before retrying, causing each caller
to start its own redundant fetch and overwrite the previous retry.

Fix: capture the promise reference before .catch() and only delete when
the cache still holds that exact promise. The first caller to run the
handler starts the single retry; all later callers see a newer promise
in the cache and reuse it via the recursive fetchLifecycleData call.

Adds a three-caller regression test.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The NS_BINDING_ABORTED errors are inherent to the abort-on-unmount
pattern: every row unmount (sort, filter, navigation) cancels its
in-flight lifecycle request, Firefox logs the cancellation, and the
cache-sharing fixes we added are complex responses to a self-inflicted
problem.

Root fix: let requests complete naturally.

- Remove AbortController, abortRef, cleanup abort, and all
  signal-passing code from useOperatorLifecycle.
- Simplify fetchLifecycleData: no more AbortError special-casing,
  Fix-B retry logic, or multi-caller stomp guard — just deduplicate
  concurrent requests via the module-level cache and let every request
  reach the backend exactly once.
- When a component unmounts mid-flight the request finishes, populates
  the cache, and React silently discards the resulting state update.
  The next render of the same operator (after sort/filter/remount)
  gets an instant cache hit.
- Drop the abort-specific test cases; replace with deduplication and
  cache-hit tests that describe the new invariants.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`:
- Around line 100-112: Guard the fetchLifecycleData callbacks in the
useOperatorLifecycle effect with an active/cancelled flag, and mark the effect
inactive during cleanup so superseded requests cannot update data, error, or
loading state. Preserve request caching and return the cleanup function from the
effect. Add a regression test that rerenders from A to B, resolves B before A,
and verifies B remains displayed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7995f5f3-1484-4bc8-933b-56ba13a73fd8

📥 Commits

Reviewing files that changed from the base of the PR and between de160a4 and 24cc23c.

📒 Files selected for processing (2)
  • frontend/packages/operator-lifecycle-manager/src/hooks/__tests__/useOperatorLifecycle.spec.ts
  • frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts

Comment on lines +100 to +112
fetchLifecycleData(cacheKey, url)
.then((result) => {
if (!controller.signal.aborted) {
setData(result);
setError(null);
setLoading(false);
}
setData(result);
setError(null);
setLoading(false);
})
.catch((err: Error) => {
if (!controller.signal.aborted) {
setData(null);
setError(err);
setLoading(false);
}
setData(null);
setError(err);
setLoading(false);
});

return () => {
controller.abort();
};
return undefined; // no cleanup: requests complete naturally

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent superseded requests from updating this hook.

When the inputs change while request A is pending, request B starts; if B resolves first and A resolves later, A unconditionally overwrites B’s state. Keep the request cacheable, but ignore callbacks from a cleaned-up effect. Add a regression test that rerenders from A to B, resolves B then A, and verifies B remains displayed.

Proposed fix
   useEffect(() => {
+    let active = true;
+
     // ...
     fetchLifecycleData(cacheKey, url)
       .then((result) => {
+        if (!active) return;
         setData(result);
         setError(null);
         setLoading(false);
       })
       .catch((err: Error) => {
+        if (!active) return;
         setData(null);
         setError(err);
         setLoading(false);
       });
 
-    return undefined; // no cleanup: requests complete naturally
+    return () => {
+      active = false;
+    };
   }, [packageName, catalogName, catalogNamespace]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fetchLifecycleData(cacheKey, url)
.then((result) => {
if (!controller.signal.aborted) {
setData(result);
setError(null);
setLoading(false);
}
setData(result);
setError(null);
setLoading(false);
})
.catch((err: Error) => {
if (!controller.signal.aborted) {
setData(null);
setError(err);
setLoading(false);
}
setData(null);
setError(err);
setLoading(false);
});
return () => {
controller.abort();
};
return undefined; // no cleanup: requests complete naturally
useEffect(() => {
let active = true;
// ...
fetchLifecycleData(cacheKey, url)
.then((result) => {
if (!active) return;
setData(result);
setError(null);
setLoading(false);
})
.catch((err: Error) => {
if (!active) return;
setData(null);
setError(err);
setLoading(false);
});
return () => {
active = false;
};
}, [packageName, catalogName, catalogNamespace]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`
around lines 100 - 112, Guard the fetchLifecycleData callbacks in the
useOperatorLifecycle effect with an active/cancelled flag, and mark the effect
inactive during cleanup so superseded requests cannot update data, error, or
loading state. Preserve request caching and return the cleanup function from the
effect. Add a regression test that rerenders from A to B, resolves B before A,
and verifies B remains displayed.

@openshift-ci

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@perdasilva: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

component/olm Related to OLM component/shared Related to console-shared jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants