Skip to content

OCPBUGS-77415: Fix infinite recursion in project access form#16069

Open
rhamilto wants to merge 1 commit intoopenshift:mainfrom
rhamilto:OCPBUGS-77415
Open

OCPBUGS-77415: Fix infinite recursion in project access form#16069
rhamilto wants to merge 1 commit intoopenshift:mainfrom
rhamilto:OCPBUGS-77415

Conversation

@rhamilto
Copy link
Member

@rhamilto rhamilto commented Feb 26, 2026

Summary

Fixes a "Maximum call stack size exceeded" error that occurs when clicking "Add access" in the project access form.

After

Screen.Recording.2026-02-26.at.8.59.26.AM.mov

Changes

  • formik-validation-fix.ts: Remove validateForm from useEffect dependencies to prevent infinite re-render loops when field arrays change
  • ProjectAccess.tsx: Fix handleReset to avoid calling resetForm() recursively by using setStatus() and setValues() directly
  • ProjectAccessForm.tsx: Fix stale data detection by comparing roleBindings against initialValues instead of current values
  • ProjectAccess.tsx: Pass live userRoleBindings to ProjectAccessForm so stale detection works correctly

Root Cause

The error occurred due to three separate issues:

  1. validateForm in the dependency array of useFormikValidationFix caused validation loops when field arrays changed
  2. handleReset called actions.resetForm() which triggered the onReset callback recursively
  3. Stale detection compared current form values against roleBindings, triggering false positives when users made local edits

Test plan

  • Navigate to Developer perspective > Project Access
  • Click "Add access" button
  • Verify no "Maximum call stack size exceeded" error occurs
  • Verify form allows adding new access entries
  • Verify "This list has been updated" alert does NOT appear when making local edits
  • Verify "This list has been updated" alert DOES appear when backend data changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented unnecessary re-renders during form validation and fixed form reset behavior so values and status restore correctly.
    • Improved staleness detection so the project access form more reliably determines when to refresh displayed data.
  • Refactor

    • Optimized form state computations to reduce repeated work and improve UI responsiveness.

@openshift-ci-robot openshift-ci-robot added jira/severity-moderate Referenced Jira bug's severity is moderate for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. labels Feb 26, 2026
@openshift-ci-robot
Copy link
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-77415, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Summary

Fixes a "Maximum call stack size exceeded" error that occurs when clicking "Add access" in the project access form.

Changes

  • formik-validation-fix.ts: Remove validateForm from useEffect dependencies to prevent infinite re-render loops when field arrays change
  • ProjectAccess.tsx: Fix handleReset to avoid calling resetForm() recursively by using setStatus() and setValues() directly
  • ProjectAccessForm.tsx: Fix stale data detection by comparing roleBindings against initialValues instead of current values
  • ProjectAccess.tsx: Pass live userRoleBindings to ProjectAccessForm so stale detection works correctly

Root Cause

The error occurred due to three separate issues:

  1. validateForm in the dependency array of useFormikValidationFix caused validation loops when field arrays changed
  2. handleReset called actions.resetForm() which triggered the onReset callback recursively
  3. Stale detection compared current form values against roleBindings, triggering false positives when users made local edits

Test plan

  • Navigate to Developer perspective > Project Access
  • Click "Add access" button
  • Verify no "Maximum call stack size exceeded" error occurs
  • Verify form allows adding new access entries
  • Verify "This list has been updated" alert does NOT appear when making local edits
  • Verify "This list has been updated" alert DOES appear when backend data changes

🤖 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-robot openshift-ci-robot added the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label Feb 26, 2026
@openshift-ci openshift-ci bot added component/dev-console Related to dev-console component/shared Related to console-shared approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Feb 26, 2026
@rhamilto
Copy link
Member Author

/cherry-pick release-4.22 release-4.21 release-4.20 release-4.19

@openshift-cherrypick-robot

@rhamilto: once the present PR merges, I will cherry-pick it on top of release-4.22 in a new PR and assign it to you.

Details

In response to this:

/cherry-pick release-4.22 release-4.21 release-4.20 release-4.19

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.

@rhamilto
Copy link
Member Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Feb 26, 2026
@openshift-ci-robot
Copy link
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-77415, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @yapei

Details

In response to this:

/jira refresh

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 a review from yapei February 26, 2026 14:09
@rhamilto
Copy link
Member Author

/cherry-pick cancel

@openshift-cherrypick-robot

@rhamilto: once the present PR merges, I will cherry-pick it on top of cancel in a new PR and assign it to you.

Details

In response to this:

/cherry-pick cancel

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.

@rhamilto
Copy link
Member Author

/cherry-pick release-4.21 release-4.20 release-4.19

@openshift-cherrypick-robot

@rhamilto: once the present PR merges, I will cherry-pick it on top of release-4.21 in a new PR and assign it to you.

Details

In response to this:

/cherry-pick release-4.21 release-4.20 release-4.19

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.

@rhamilto
Copy link
Member Author

/assign @yapei
/assign @cajieh

Copy link
Contributor

@cajieh cajieh left a comment

Choose a reason for hiding this comment

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

/lgtm

actions.resetForm({ status: { success: null }, values: initialValues });
const handleReset = (values: FormikValues, actions: FormikHelpers<FormikValues>) => {
actions.setStatus({ success: null });
actions.setValues(initialValues.projectAccess ? initialValues : values);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Would initialValues.projectAccess be falsy given line 61 sets it explicitly? The fallback to values looks unreachable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, @cajieh. Updated.

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Feb 27, 2026
@openshift-ci openshift-ci bot removed the lgtm Indicates that a PR is ready to be merged. label Feb 27, 2026
@openshift-ci-robot
Copy link
Contributor

@rhamilto: This pull request references Jira Issue OCPBUGS-77415, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @yapei

Details

In response to this:

Summary

Fixes a "Maximum call stack size exceeded" error that occurs when clicking "Add access" in the project access form.

After

Screen.Recording.2026-02-26.at.8.59.26.AM.mov

Changes

  • formik-validation-fix.ts: Remove validateForm from useEffect dependencies to prevent infinite re-render loops when field arrays change
  • ProjectAccess.tsx: Fix handleReset to avoid calling resetForm() recursively by using setStatus() and setValues() directly
  • ProjectAccessForm.tsx: Fix stale data detection by comparing roleBindings against initialValues instead of current values
  • ProjectAccess.tsx: Pass live userRoleBindings to ProjectAccessForm so stale detection works correctly

Root Cause

The error occurred due to three separate issues:

  1. validateForm in the dependency array of useFormikValidationFix caused validation loops when field arrays changed
  2. handleReset called actions.resetForm() which triggered the onReset callback recursively
  3. Stale detection compared current form values against roleBindings, triggering false positives when users made local edits

Test plan

  • Navigate to Developer perspective > Project Access
  • Click "Add access" button
  • Verify no "Maximum call stack size exceeded" error occurs
  • Verify form allows adding new access entries
  • Verify "This list has been updated" alert does NOT appear when making local edits
  • Verify "This list has been updated" alert DOES appear when backend data changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
  • Fixed form validation behavior to prevent unnecessary interface re-rendering during form operations.
  • Enhanced project access form state detection to accurately determine when configuration data requires refreshing.

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

Changes span three files. In formik-validation-fix.ts the effect no longer includes validateForm in its dependency array, with comments and an eslint-disable added. In ProjectAccess.tsx several values are memoized (userRoleBindings, initialValues, memoizedRoleBindings), the loading guard is moved after initialValues computation, and handleReset signature/behavior was changed to use setStatus and setValues (typed Formik helpers). In ProjectAccessForm.tsx the staleness check in useEffect now compares against initialValues.projectAccess.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the primary fix: preventing infinite recursion in the project access form by addressing validateForm dependencies, recursive resetForm calls, and stale-data detection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx (1)

115-118: Use resetForm() instead of setValues() to properly reset the form baseline and clear interaction state.

Line 117 uses setValues(initialValues), which only updates current values without resetting the initialValues baseline used for dirty tracking. This leaves dirty, touched, and errors states uncleared—stale validation messages may still appear after reset. The handleSubmit already uses the correct pattern (resetForm()) on success; apply it here too for consistency:

actions.resetForm({
  values: initialValues,
  status: { success: null }
})

This ensures the baseline, dirty flag, and interaction state are all properly reset together, as expected in a true reload/reset UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx`
around lines 115 - 118, The handleReset function currently calls
actions.setValues(initialValues), which only updates current values and leaves
the Formik baseline and interaction state (dirty, touched, errors) unchanged;
replace that call with actions.resetForm({ values: initialValues, status: {
success: null } }) so the form's initialValues baseline, dirty/touched state and
status are all reset together—update the handleReset implementation (referencing
handleReset, initialValues, and the Formik actions object) to use resetForm
instead of setValues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx`:
- Around line 159-163: ProjectAccess passes a new inline object for the
roleBindings prop on every render which retriggers ProjectAccessForm's
useEffect; fix by memoizing that object: create a memo (e.g., using
React.useMemo) that returns { projectAccess: userRoleBindings } with
[userRoleBindings] as the dependency and pass the memoized value to the
ProjectAccessForm roleBindings prop (replace the inline literal currently passed
on ProjectAccessForm).

---

Nitpick comments:
In
`@frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx`:
- Around line 115-118: The handleReset function currently calls
actions.setValues(initialValues), which only updates current values and leaves
the Formik baseline and interaction state (dirty, touched, errors) unchanged;
replace that call with actions.resetForm({ values: initialValues, status: {
success: null } }) so the form's initialValues baseline, dirty/touched state and
status are all reset together—update the handleReset implementation (referencing
handleReset, initialValues, and the Formik actions object) to use resetForm
instead of setValues.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between eb23957 and cb1eaad.

📒 Files selected for processing (3)
  • frontend/packages/console-shared/src/hooks/formik-validation-fix.ts
  • frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx
  • frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx

This fixes a "Maximum call stack size exceeded" error that occurs
when clicking "Add access" in the project access form.

Changes:
- Remove validateForm from useFormikValidationFix dependencies to
  prevent infinite re-render loops when field arrays change
- Fix handleReset to avoid calling resetForm() recursively by using
  setStatus() and setValues() directly
- Fix stale data detection by comparing roleBindings against
  initialValues instead of current values, and pass live
  userRoleBindings to ProjectAccessForm

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@rhamilto
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In
`@frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx`:
- Around line 126-129: handleReset currently only resets status and values but
leaves Formik validation metadata (errors/touched) which causes stale error UI;
update handleReset (the function named handleReset with signature (_values:
FormikValues, actions: FormikHelpers<FormikValues>)) to also clear validation
state by calling actions.setErrors({}) and actions.setTouched({}), and
optionally actions.setSubmitting(false) to ensure the form UI and submit state
are fully reset.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between cb1eaad and 22cf7b6.

📒 Files selected for processing (3)
  • frontend/packages/console-shared/src/hooks/formik-validation-fix.ts
  • frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx
  • frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx

Comment on lines +126 to 129
const handleReset = (_values: FormikValues, actions: FormikHelpers<FormikValues>) => {
actions.setStatus({ success: null });
actions.setValues(initialValues);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "package.json" -o -name "package-lock.json" | head -5

Repository: openshift/console

Length of output: 276


🌐 Web query:

Formik resetForm() vs setValues() formik documentation touched errors submitCount

💡 Result:

resetForm() vs setValues() (Formik)

resetForm(nextState?)

Use when you want to re-initialize / “start over” the form state.

  • Resets the form back to its initial state when called with no args. (formik.org)
  • If you pass nextState, Formik uses it to update form state, and also treats nextState.values as the new base “initial state” (updating initialValues and related initialTouched, initialErrors, initialStatus). (formik.org)
  • nextState can include: values, errors, touched, isSubmitting, isValidating, status, submitCount. (formik.org)

Practical effect: clears/overwrites touched and errors (and can reset submitCount) because you’re resetting the whole Formik state, not just values. (formik.org)

setValues(values, shouldValidate?)

Use when you only want to programmatically replace the current field values.

  • Sets values imperatively; does not say it resets touched, errors, or submitCount. (formik.org)
  • By default it will trigger validation if validateOnChange is true; pass shouldValidate = false to skip. (formik.org)

Practical effect: keeps touched, errors, submitCount as-is unless validation changes errors due to the new values. (formik.org)


How touched, errors, submitCount behave around submit/reset

  • On submit (handleSubmit / submitForm), Formik will:
    • Touch all fields
    • set isSubmitting = true
    • increment submitCount (formik.org)
  • If validation finds errors, submission aborts and Formik sets errors and flips isSubmitting back to false. (formik.org)
  • submitCount increases when handleSubmit is called and resets after handleReset (which resets the form to initial state). (formik.org)

Rule of thumb

  • Use resetForm() when you want a true reset / reinitialize (including touched / submitCount, and optionally redefining “initial” values). (formik.org)
  • Use setValues() when you want to update values in place without resetting the rest of Formik’s state. (formik.org)

Citations:


🏁 Script executed:

cat -n frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx | sed -n '100,150p'

Repository: openshift/console

Length of output: 2233


🏁 Script executed:

grep -A 2 -B 2 '"formik"' frontend/package.json

Repository: openshift/console

Length of output: 192


🏁 Script executed:

cat -n frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx | sed -n '1,100p'

Repository: openshift/console

Length of output: 4289


🏁 Script executed:

grep -E "(touched|errors|getFieldMeta|error|className.*error)" frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx | head -20

Repository: openshift/console

Length of output: 43


🏁 Script executed:

find . -name "ProjectAccessForm.tsx" -o -name "ProjectAccessForm.ts" -o -name "ProjectAccessForm.jsx"

Repository: openshift/console

Length of output: 144


🏁 Script executed:

cat -n frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx | head -150

Repository: openshift/console

Length of output: 4862


🏁 Script executed:

grep -E "(touched|errors|getFieldMeta|hasError|error)" frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx | head -15

Repository: openshift/console

Length of output: 183


Reset handler should clear Formik validation metadata to prevent stale error UI.

The handleReset() method only resets values and status, but leaves the errors and touched state intact. Since the form displays validation errors and uses the errors object to control submit state (line 65), users will see stale validation messages even after clicking reset.

Suggested fix
   const handleReset = (_values: FormikValues, actions: FormikHelpers<FormikValues>) => {
-    actions.setStatus({ success: null });
-    actions.setValues(initialValues);
+    actions.setStatus(null);
+    actions.setErrors({});
+    actions.setTouched({});
+    actions.setValues(initialValues, false);
   };
📝 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
const handleReset = (_values: FormikValues, actions: FormikHelpers<FormikValues>) => {
actions.setStatus({ success: null });
actions.setValues(initialValues);
};
const handleReset = (_values: FormikValues, actions: FormikHelpers<FormikValues>) => {
actions.setStatus(null);
actions.setErrors({});
actions.setTouched({});
actions.setValues(initialValues, false);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx`
around lines 126 - 129, handleReset currently only resets status and values but
leaves Formik validation metadata (errors/touched) which causes stale error UI;
update handleReset (the function named handleReset with signature (_values:
FormikValues, actions: FormikHelpers<FormikValues>)) to also clear validation
state by calling actions.setErrors({}) and actions.setTouched({}), and
optionally actions.setSubmitting(false) to ensure the form UI and submit state
are fully reset.

@cajieh
Copy link
Contributor

cajieh commented Feb 27, 2026

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Feb 27, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 27, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cajieh, rhamilto

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

The pull request process is described 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

@openshift-ci openshift-ci bot removed the lgtm Indicates that a PR is ready to be merged. label Feb 27, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 27, 2026

New changes are detected. LGTM label has been removed.

@rhamilto
Copy link
Member Author

@cajieh, mind retagging? Claude just forced an errant push.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 27, 2026

@rhamilto: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-gcp-console 22cf7b6 link true /test e2e-gcp-console

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.

@yapei
Copy link
Contributor

yapei commented Feb 28, 2026

Now user is able to view and edit Project access settings succesfully

project-access.mov

However I'm a little curious why there is an initial kube:admin record shown for every project? It only happens when we log in as kubeadmin user

kubeadmin-record.mov

If I'm logging in as other cluster-admin user, default list is empty
pm1

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. component/dev-console Related to dev-console component/shared Related to console-shared jira/severity-moderate Referenced Jira bug's severity is moderate for the branch this PR is targeting. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. 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.

5 participants