fullstackhero/dotnet-starter-kit · error · CustomException
Setting this parent would create a cycle.
Error message
Setting this parent would create a cycle.
What it means
UpdateCategory walks the ancestor chain starting at the proposed parent, tracking visited ids; if the walk revisits an id (or reaches the category itself) the new parent would create a cycle, so it throws CustomException with 400 BadRequest. This prevents parent loops like A→B→A in the category tree.
Solutions
- Choose a parent that is not a descendant of the category being moved.
- Move the intermediate nodes in the correct order (detach subtree first, then re-parent).
- Render the tree in the UI with descendants of the edited node disabled as parent options.
- If a legitimate restructure is needed, perform multiple sequential updates via a valid intermediate state.
Example fix
// before: move A under its descendant B
await api.updateCategory({ id: a, parentCategoryId: b }); // 400 cycle
// after: move B under A first (if that's the real intent), or pick a non-descendant parent
await api.updateCategory({ id: b, parentCategoryId: a }); Defensive patterns
Strategy: validation
Validate before calling
// client-side: disallow selecting any descendant as parent
const descendants = collectDescendants(tree, categoryId);
if (descendants.includes(payload.parentCategoryId)) {
return formError('Cannot move a category under its own descendant.');
} Try / catch
try { await api.updateCategory(payload); }
catch (e) { if (e.status === 400 && /cycle/i.test(e.message)) formError('That parent would create a cycle'); else throw e; } Prevention
- Disable descendant nodes in the parent picker
- Validate the full parent chain client-side before submit
- Be careful with subtree moves and restructures
When it happens
Trigger: PATCH update setting parent P such that P's ancestor chain already contains (or leads back to) the category being updated — e.g. moving A under its own descendant B.
Common situations: Reorganizing a tree by moving a whole subtree under one of its children; concurrent edits where two users swap parents of nested categories; import scripts that don't topologically order rows.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A category cannot be its own parent.
- A brand with name ' ' already exists.
- Brand not found.
- Brand not found.
- Brand not found.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/e9789eb8762db647.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/UpdateCategory/UpdateCategoryCommandHandler.cs:39
if (command.ParentCategoryId is { } parentId)
{
if (parentId == category.Id)
{
throw new CustomException(
"A category cannot be its own parent.",
(IEnumerable<string>?)null,
HttpStatusCode.BadRequest);
}
// Walk parent chain to detect cycles (parent → ancestor of self)
var visited = new HashSet<Guid> { category.Id };
Guid? cursor = parentId;
while (cursor is { } cur)
{
if (!visited.Add(cur))
{
throw new CustomException(
"Setting this parent would create a cycle.",
(IEnumerable<string>?)null,
HttpStatusCode.BadRequest);
}
cursor = await dbContext.Categories
.Where(c => c.Id == cur)
.Select(c => c.ParentCategoryId)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
}
}
category.Update(command.Name, command.Description, command.ParentCategoryId);
bool slugTaken = await dbContext.Categories
.AnyAsync(c => c.Slug == category.Slug && c.Id != category.Id, cancellationToken)
.ConfigureAwait(false);
if (slugTaken)View on GitHub (pinned to 3f2959e683)