Repository navigation
layout: a pane writing its tabs back no longer reads freed ids - #476
Merged
Merged
Conversation
`Layout.State.assign` freed a region's old id strings (`setAssignment`), then took the new ids out of every other region by reading the caller's list. A pane adds or drops a tab by reading its assignment, editing it and writing it back (`Workspace.addTab`), so that list borrows the very strings just freed. Fizzy crashed there on 2026-10-16 (`mem.eql` in `evictIdFromOthers`), on an agent's `pixi.create` opening beside the person into a pane that had tabs. `assign` now evicts by the region's new, owned copy, so its input may borrow from any assignment, its own included. It takes that copy's key and list out of the map first: rewriting the other regions can grow the map (`getOrPut` makes room before it looks) and move its entries. The SDK's `assignedSurfaces` doc says so: read, edit, write. `evictIdFromOthers` kept at most 32 regions and 32 tabs in fixed buffers and swallowed its errors: a pane of 40 tabs that gave one up came back with 32. It now uses lists, and its errors reach `assign`'s caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
force-pushed
the
layout/assign-borrowed-ids
branch
from
October 17, 2026 13:34
b5b6070 to
5c64103
Compare
foxnne
marked this pull request as ready for review
October 17, 2026 13:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A crash fix, with no plan behind it.
What changes
Fizzy crashed on 2026-10-16. It was a Debug build of
mainaround #461, at SDK 0.2.28, and it segfaulted inmem.eqlinsideLayout.State.evictIdFromOthers. The path wasassign←fizzyAssignSurfaces←Workbench.showBeside←Editor.newFile← pixi'screateDocument. An agent'spixi.createwas opening a sprite beside the person (host.openBeside, #442), into a pane that already had tabs.Cause:
State.assigncalledsetAssignment, which frees the region's old id strings. It then evicted each new id from every other region, reading the ids from the caller's list. A pane adds a tab by reading its assignment, appending to it and writing it back (Workspace.addTab;removeTab,insertTab,swapTabIdandWorkspaces.closework the same way). So the caller's list borrows the strings that were just freed, and the eviction compared against freed memory. Most of the time that read only returns garbage. It faults once the allocator has handed the freed slot's page back to the OS, which is why it took a long session with tabs opened and closed.Fix:
assignnow evicts by the region's new, owned copy and its stored key. Nothing it reads after the free can be borrowed from what was freed. A caller's list may therefore borrow from any assignment, its own included. The SDK doc forassignedSurfaces(Host and EditorAPI) now says so: "read, edit, write" was already how every caller used it. It is a comment change only, and the fingerprint does not move.The ownership is fixed in
State, not with a guard inevictIdFromOthers, so every caller of the read-edit-write pattern is safe without each one copying first.Found in review, fixed here too:
assignnow takes its own entry's key and list out of the map before evicting. Rewriting another region runsgetOrPut, which grows the map before it looks up even an existing key, so a pointer into the entry could go stale mid-loop. This was a second use-after-free of the same kind.evictIdFromOtherskept at most 32 regions and 32 tabs in fixed buffers and swallowedsetAssignmenterrors. A pane of 40 tabs that gave one up came back with 32, with no error. It now uses lists, and its errors reachassign's caller.SDK impact
assignedSurfacesonly)sdk/src/version.zig,sdk_versionuntouched, PR labelledsdksdk_version, lists thesdkPRs since the lastsdk-v*tagVerified
tests/integration.zig). It usesstd.heap.page_allocator, so every free unmaps and a read of a freed id faults every time. Without the fix it crashes with the same stack as the report (mem.eql←evictIdFromOthers←assign). With the fix it passes.key_ptr.host.openBeside()into two panes, closing every other one. It checks that the person stays in their pane and that the last document is active beside them. That test runs onsmp_allocator, so it exercises the path but would not reliably catch the read on its own.--profileset to a fresh directory, with pixi and agentmainbuilt against this tree (--fork). Through the agent'sfizzy-mcp, I ran 8pixi.createcalls with paths and 3fizzy.closecalls. That ended with two panes, holdings2, s4ands6, s7, s8, and fizzy kept running. The same sequence run 3 times on a build without the fix did not crash either, because the freed slots' pages weren't unmapped; the page-allocator test is the deterministic proof.check-fmt,zig build,testandtest-integration(53/53 steps succeeded, 558 tests), pluscheck-web,test-sdk-versionandcheck-tests-reached.Follow-ups
None.
🤖 Generated with Claude Code