fix(pages): prevent circular parent references#61
Conversation
Add a `beforeChange` hook that validates the parent field by walking up the ancestor chain and detecting cycles using a visited-nodes Set. Handles: - Direct self-reference (doc.parent = doc.id) - Two-node cycles (A -> B -> A) - Deep cycles (A -> B -> C -> A) Tests added for all three cases plus a valid parent scenario. Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com>
| ...(incomingCollectionConfig.hooks?.afterChange || []), | ||
| ], | ||
| beforeChange: [ | ||
| preventCircularParentReference, |
There was a problem hiding this comment.
This looks good for the manually tested admin/API path, but I think the placement here is still fragile. Because this validator runs before consumer beforeChange hooks, a later hook can still modify data.parent and introduce a cycle after this check has already passed. Would it make sense to enforce this at the final authoritative boundary for the resolved parent value instead of relying on early hook order?
srjaykikani
left a comment
There was a problem hiding this comment.
Thanks for putting this together. I manually tested the localized dev app flow for this change:
- created a root page
- created a child under that root
- created a grandchild under that child
- edited the child and tried to set its parent to the grandchild
That save was rejected with a validation error, and the persisted parent stayed unchanged, so the tested path is working as expected.
One structural concern I still have: the new cycle check is added as the first beforeChange hook in createPageCollectionConfig. Since consumer-provided beforeChange hooks run after that, a later hook can still rewrite data.parent after this validator has already passed. So this fixes the path I tested, but it does not fully guarantee the invariant at the final authoritative save boundary.
Also, pnpm lint is currently failing only on pages/src/hooks/preventCircularParentReference.ts in this PR.
Overall, the behavior looks correct for the manually tested case, but I think the hook-order piece is still worth tightening so the fix addresses the root cause rather than only the current path.
|
@copilot run pnpm lint and fix any issues. In a separate commit please change the order of the before change hooks to address Jays comments. |
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
5 similar comments
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@jhb-dev The model You can try again without specifying a model (just If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: |
|
@copilot run pnpm lint and fix any issues. In a separate commit please change the order of the before change hooks to address Jays comments. |
…ence hook Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com> Agent-Logs-Url: https://github.com/jhb-software/payload-plugins/sessions/1cb9190d-1d15-4f4f-8ce2-77d993183e14
Ensures the cycle check validates the final resolved parent value, after any consumer beforeChange hooks have had a chance to modify data. Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com> Agent-Logs-Url: https://github.com/jhb-software/payload-plugins/sessions/1cb9190d-1d15-4f4f-8ce2-77d993183e14
Done in two separate commits:
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot, are you able to access the ci runs of this PR to see if all were successful? If not please fix. |
Fixes CI format check that failed because eslint --fix added single-line curly braces that Prettier reformats to multi-line. Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com> Agent-Logs-Url: https://github.com/jhb-software/payload-plugins/sessions/7b075ce6-3aec-4ce4-a145-299ec9992d7f
Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com> Agent-Logs-Url: https://github.com/jhb-software/payload-plugins/sessions/7b075ce6-3aec-4ce4-a145-299ec9992d7f
Yes — the CI format job was failing. The I also accidentally committed a |
|
@copilot revert the gitignore change. Also, will the parent find calls also be executed if the parent has not changed? E. g. the parent in the update is the same as before? |
…nchanged - Restore pages/.gitignore: fix corrupted /dist line (was merged with package-lock.json due to missing trailing newline), remove the package-lock.json entry added in the previous session - Skip the ancestor-chain findByID walk on updates when the parent field value has not changed, avoiding unnecessary DB queries Co-authored-by: jhb-dev <20878653+jhb-dev@users.noreply.github.com> Agent-Logs-Url: https://github.com/jhb-software/payload-plugins/sessions/64bad0b7-e3af-4159-b233-e7d9e3341c53
Done in
|
preventCircularParentReferencebeforeChange hookPageCollectionConfig.tspreventCircularParentReferenceto run after user beforeChange hooks (per Jay's review).gitignorecorruption and removepackage-lock.jsonentryOriginal prompt
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.