You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I found the cause. It's a bug in how 0.8.x generates the order key when a new page is placed first under a parent. Once a parent has had seven pages created that way, the next generated key is one that already exists, so every later create under that parent fails. It's already fixed on main, which ships as 0.9.0. I assumed the reporter runs 0.8.1, since 0.9.0 isn't released yet.
What happens (v0.8.1)
When you click "+" on a page, the web app sends POST /api/docs with a parent_id and no after_id. The server then gives the new page a key just below the first existing subpage: sort_key_between(None, first_sibling), which calls decrement() in crates/knot-storage/src/lexorank.rs at v0.8.1.
decrement() breaks once the keys reach "0": for "0" it returns "0m", which sorts after"0". Running the 0.8.1 code for repeated "new subpage" clicks under one parent gives:
1 m 2 b 3 5 4 2 5 1 6 0 7 0m 8 0m ← collision
The 8th create produces "0m" again. That breaks the UNIQUE (workspace_id, parent_id, sort_key) constraint, and the server returns 409 doc.sort_key_conflict. The web app turns any error into the generic "Couldn't create document" toast (DocTree.tsx:113). From then on, every create under that parent fails the same way, because the first sibling is still "0". Archived subpages keep their keys, so archiving pages doesn't help.
Why it matches the report
Role doesn't matter: it's a key collision, not a permission check.
Top level works: the old unique constraint treated NULL parents as distinct, so duplicate keys at the top level were silently accepted.
Deeper levels work: those groups just haven't had seven pages inserted at the top yet.
Only one parent is affected: the busy top-level page whose subpages are the "2nd level" the reporter means. A smaller earlier sign: the 7th page created there appeared second, not first.
How to confirm on their instance
In the browser's network tab, the failing POST /api/docs should return 409 with doc.sort_key_conflict.
Or run this query, expecting to see both 0 and 0m:
SELECT sort_key, title, archived_at FROM documents
WHERE parent_id ='<id of the top-level page>'ORDER BY sort_key COLLATE "C";
The 20261024120000_heal_sort_keys migration rewrites the keys of any group containing an invalid key like "0".
The new key code rejects "0" as a neighbour, and placement::place then rewrites the group's keys and tries again.
placement_rebalances_invalid_neighbours in crates/knot-storage/tests/placement.rs tests the "0" case directly.
Upgrading should resolve it. Until then, the reporter can create the page somewhere else (for example at the top level) and drag it under the parent, because a move computes its key differently. Moving the page whose key is "0" further down also unblocks creates for a while, but the same problem comes back after a few more pages.
Activity
[-]Cannot create new subpage[/-][+]Cannot create new subpage in group[/+]christianhuening commentedon Oct 28, 2026
I found the cause. It's a bug in how 0.8.x generates the order key when a new page is placed first under a parent. Once a parent has had seven pages created that way, the next generated key is one that already exists, so every later create under that parent fails. It's already fixed on main, which ships as 0.9.0. I assumed the reporter runs 0.8.1, since 0.9.0 isn't released yet.
What happens (v0.8.1)
POST /api/docswith aparent_idand noafter_id. The server then gives the new page a key just below the first existing subpage:sort_key_between(None, first_sibling), which callsdecrement()incrates/knot-storage/src/lexorank.rsat v0.8.1.decrement()breaks once the keys reach"0": for"0"it returns"0m", which sorts after"0". Running the 0.8.1 code for repeated "new subpage" clicks under one parent gives:"0m"again. That breaks theUNIQUE (workspace_id, parent_id, sort_key)constraint, and the server returns409 doc.sort_key_conflict. The web app turns any error into the generic "Couldn't create document" toast (DocTree.tsx:113). From then on, every create under that parent fails the same way, because the first sibling is still"0". Archived subpages keep their keys, so archiving pages doesn't help.Why it matches the report
NULLparents as distinct, so duplicate keys at the top level were silently accepted.How to confirm on their instance
POST /api/docsshould return 409 withdoc.sort_key_conflict.0and0m:Fix
0.9.0 already fixes this (from #52):
20261024120000_heal_sort_keysmigration rewrites the keys of any group containing an invalid key like"0"."0"as a neighbour, andplacement::placethen rewrites the group's keys and tries again.placement_rebalances_invalid_neighboursincrates/knot-storage/tests/placement.rstests the"0"case directly.Upgrading should resolve it. Until then, the reporter can create the page somewhere else (for example at the top level) and drag it under the parent, because a move computes its key differently. Moving the page whose key is
"0"further down also unblocks creates for a while, but the same problem comes back after a few more pages.christianhuening commentedon Oct 29, 2026
@maresac please check whether the issue is still open for you. knot 0.9.0 should've fixed that