Title: deepStrictEqual rejects structurally equal array elements if a previous call contained a cycle and a previous array element is reference equal to the item in one of the arrays · Issue #62422 · nodejs/node · GitHub
Open Graph Title: deepStrictEqual rejects structurally equal array elements if a previous call contained a cycle and a previous array element is reference equal to the item in one of the arrays · Issue #62422 · nodejs/node
X Title: deepStrictEqual rejects structurally equal array elements if a previous call contained a cycle and a previous array element is reference equal to the item in one of the arrays · Issue #62422 · nodejs/node
Description: Version v25.8.1 Platform Linux codespaces-c0c7e6 6.8.0-1044-azure #50~22.04.1-Ubuntu SMP Wed Dec 3 15:13:22 UTC 2025 x86_64 GNU/Linux Subsystem assert What steps will reproduce the bug? I have included a test which reproduces the bug in ...
Open Graph Description: Version v25.8.1 Platform Linux codespaces-c0c7e6 6.8.0-1044-azure #50~22.04.1-Ubuntu SMP Wed Dec 3 15:13:22 UTC 2025 x86_64 GNU/Linux Subsystem assert What steps will reproduce the bug? I have incl...
X Description: Version v25.8.1 Platform Linux codespaces-c0c7e6 6.8.0-1044-azure #50~22.04.1-Ubuntu SMP Wed Dec 3 15:13:22 UTC 2025 x86_64 GNU/Linux Subsystem assert What steps will reproduce the bug? I have incl...
Opengraph URL: https://github.com/nodejs/node/issues/62422
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"deepStrictEqual rejects structurally equal array elements if a previous call contained a cycle and a previous array element is reference equal to the item in one of the arrays","articleBody":"### Version\n\nv25.8.1\n\n### Platform\n\n```text\nLinux codespaces-c0c7e6 6.8.0-1044-azure #50~22.04.1-Ubuntu SMP Wed Dec 3 15:13:22 UTC 2025 x86_64 GNU/Linux\n```\n\n### Subsystem\n\nassert\n\n### What steps will reproduce the bug?\n\nI have included a test which reproduces the bug in a draft PR in my fork: https://github.com/CraigMacomber/node/pull/1\n\n### How often does it reproduce? Is there a required condition?\n\nThis can only happen when cycle detection has been enabled due to some prior deep equality assert containing a cycle.\n\nIt also only happens when there are some reference equal values in the expected or actual structures which are reference equal in one but not the other.\n\n### What is the expected behavior? Why is that the expected behavior?\n\nI expect deepStrictEqual to never throw when objects have the same structure but are not reference-equal.\n\nI expect deepStrictEqual's behaviour should not be impacted by previous calls to deepStrictEqual.\n\nI have these expectations since they seem like intuitive assumptions to infer based on the [current documentation for deepStrictEqual](https://nodejs.org/api/assert.html#assertdeepstrictequalactual-expected-message).\n\n### What do you see instead?\n\nBoth the above expectations are violated, see how the referenced test triggers a \"Values have same structure but are not reference-equal\" error from deepStrictEqual, but only if a previous call contained a cycle.\n\n### Additional information\n\nI was going to try and include a fix and not just a reproduction of the issue, but the lack of documentation and fine-grained testing of the functions in lib/internal/util/comparisons.js made me not have the confidence to attempt a fix.\n\nHere is a copy of the test from the linked PR so that thus bug is fully self-contained.\n\nNote that AI was used in the construction and documentation of this bug reproduction, but I manually confirmed it reproduces on current main ( 8e8b98d3aafa018f7b30cf7878bef057acf2d235 ), v24.14.0 and v25.8.1, and that it does not reproduce in v22.22.1.\nI also looked at the implementation of handleCycles in lib/internal/util/comparisons.js and confirmed that the explanation given for why this issue occurs is plausible, but I have not fully confirmed it to be accurate.\n\nThis issue is not blocking any of my work: I just happened to run into it once and took that opportunity to minimize a reproduction or it. I do not expect to be following up on this, and do not need it prioritized in any way. I simply hope that this bug report is useful to others to help improve the quality of Node.JS.\n\nFeel free to use my test/repro upstream as a regression test if this gets fixed.\n\n```javascript\n\n// Confirmed to fail in Node.JS v24.14.0 and v25.8.1\n// Regressed from v22.22.1 which works as expected.\n// \n// Node.js's `deepStrictEqual` (and `strict.deepEqual`) uses an internal\n// `detectCycles` function that starts with `memos = null` (no cycle\n// detection). The first time a comparison throws during the null-memos\n// path — typically a stack overflow caused by comparing two circular\n// structures — `detectCycles` is permanently replaced by `innerDeepEqual`,\n// which passes a live `memos` object through every recursive call.\n//\n// In that memo-enabled mode the cycle-detection set (`memos.set`) is\n// seeded with the *current* val2 (`memos.d`) when it is first created.\n// That seed is never removed after the nested comparison returns, so when\n// the same expected object reference appears as val2 in a sibling\n// comparison, `set.add(sharedRef)` is a no-op. The invariant\n// `originalSize === set.size - 2` then fails (only one new item was added\n// instead of two), and Node.js incorrectly concludes the structures are\n// not equal.\ntest(\"deepStrictEqual rejects structurally equal arrays when expected has a shared reference and cycle detection is active\", () =\u003e {\n // `actual` has two *distinct* objects with identical content.\n // `expected` reuses the *same* object reference at both positions.\n const sharedExpected = { outer: { inner: 0 } };\n const actualValues = [{ outer: { inner: 0 } }, { outer: { inner: 0 } }];\n const expectedValues = [sharedExpected, sharedExpected];\n\n // Works, but only if no cycles have been processed before running this test.\n assert.deepStrictEqual(actualValues, expectedValues);\n\n // Activate cycle-detection mode permanently in this process\n // by comparing two isomorphic circular objects.\n // The first attempt with null memos causes a stack overflow;\n // the catch handler replaces detectCycles with innerDeepEqual for all future calls.\n const circA = {};\n circA.self = circA;\n const circB = {};\n circB.self = circB;\n assert.deepStrictEqual(circA, circB); // triggers the permanent switch\n\n // Individual element comparisons always pass …\n assert.deepStrictEqual(actualValues[0], expectedValues[0]);\n assert.deepStrictEqual(actualValues[1], expectedValues[1]);\n\n // The combined comparison now fails because Node.js's\n // cycle-detection set still contains `sharedExpected` from the first\n // element's comparison when the second element is evaluated.\n // Fails with:\n // AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal...\n assert.deepStrictEqual(actualValues, expectedValues);\n});\n\n```","author":{"url":"https://github.com/CraigMacomber","@type":"Person","name":"CraigMacomber"},"datePublished":"2026-03-24T19:25:43.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/62422/node/issues/62422"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:0af31c09-9ba3-b28f-d17a-9cc1ed0b12d6 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CE50:B5247:11D5F6F:182AF2B:6A4C75CC |
| html-safe-nonce | 8c222458241acf154a36b1090e692dc21a94a026cde3aa2a829e7456402b7fac |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRTUwOkI1MjQ3OjExRDVGNkY6MTgyQUYyQjo2QTRDNzVDQyIsInZpc2l0b3JfaWQiOiI3MjQwMjczNjUxMDk3Njk1NjkyIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 31bf5e409626b6a980d52970cdc9947f39cd9f615a02e6553ff51fd931add18a |
| hovercard-subject-tag | issue:4130164656 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/62422/issue_layout |
| twitter:image | https://opengraph.githubassets.com/85468a2d49221ebcc084500a91bd8acf84e9be6439a947c6b4dd6a0219d01a9d/nodejs/node/issues/62422 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/85468a2d49221ebcc084500a91bd8acf84e9be6439a947c6b4dd6a0219d01a9d/nodejs/node/issues/62422 |
| og:image:alt | Version v25.8.1 Platform Linux codespaces-c0c7e6 6.8.0-1044-azure #50~22.04.1-Ubuntu SMP Wed Dec 3 15:13:22 UTC 2025 x86_64 GNU/Linux Subsystem assert What steps will reproduce the bug? I have incl... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | CraigMacomber |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3d11bb817438277de2a940854450e83a7d32b6aeb5014e9e6b00a6423900251c |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 56ac743bebb13694b888673bb7257f4b97a4b7fd |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width