Skip to content

fix(webkit/window): publish state and layout saves atomically - #44

Merged
Snider merged 2 commits into
mainfrom
fix/window-state-atomic-write
Aug 8, 2026
Merged

fix(webkit/window): publish state and layout saves atomically#44
Snider merged 2 commits into
mainfrom
fix/window-state-atomic-write

Conversation

@Snider

@Snider Snider commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Two processes sharing the Core config directory (lthn + core/ide) can splice window_state.json under truncate-and-write — production carried exactly that corruption (complete short document + stale tail, every load failing with invalid character after top-level value). State and layout saves now publish via a uniquely-suffixed temp file + atomic rename.

Receipts: library suite 58 packages ok; new SaveAtomic Good/Ugly tests pass under -race (the pre-existing TestTaskEvalJS_Good data race on main is untouched and unrelated); vet + gofmt clean.

🤖 Generated with Claude Code
Co-Authored-By: Virgil virgil@lethean.io

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability when saving window layouts and state.
    • Prevented partially written or corrupted configuration files during interrupted or concurrent saves.
    • Existing file permissions and error reporting remain unchanged.
  • Tests

    • Added coverage confirming saved window state can be read successfully.
    • Added checks to ensure concurrent saves do not produce invalid or mixed configuration data.

lthn and core/ide both persist window state into the shared Core
config directory. StateManager.save and LayoutManager.save wrote with
plain truncate-and-write, so two processes racing the same file could
splice one writer's short document onto the tail of the other's longer
one — window_state.json arrived in production exactly that shape (a
complete 538-byte document followed by 145 bytes of the previous
683-byte save), after which every load fails with "invalid character
after top-level value" and all saved positions are lost.

coreWriteFileAtomic writes a uniquely-suffixed sibling temp file and
renames it into place: rename is atomic on POSIX, so readers and
racing writers only ever observe complete documents. The suffix must
be unique per writer — a fixed ".tmp" would move the same splice into
the temp file. Mirrors core/go's Fs.WriteAtomic shape with the mode
preserved.

The Ugly test runs the two-manager storm against one path and asserts
every observed read parses; it catches the old splice probabilistically
and the new path deterministically.

Co-Authored-By: Virgil <virgil@lethean.io>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Snider, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b369a689-41c4-4f8b-bdab-f7302d8a7db6

📥 Commits

Reviewing files that changed from the base of the PR and between fc1ddd6 and 800ef5f.

📒 Files selected for processing (2)
  • go/display/webkit/pkg/window/core_helpers.go
  • go/display/webkit/pkg/window/state_test.go
📝 Walkthrough

Walkthrough

Changes

Atomic persistence

Layer / File(s) Summary
Atomic write path
go/display/webkit/pkg/window/core_helpers.go, go/display/webkit/pkg/window/layout.go, go/display/webkit/pkg/window/state.go
coreWriteFileAtomic writes through a unique temporary file and atomically renames it into place. Layout and state persistence now use this helper.
Atomic save validation
go/display/webkit/pkg/window/state_test.go
Tests verify valid state JSON and concurrent saves without invalid or spliced documents.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: atomic publication of WebKit window state and layout saves.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go/display/webkit/pkg/window/core_helpers.go`:
- Around line 40-41: Update the write-failure branch in the surrounding
temporary-file helper to remove tmp before returning the error, ensuring cleanup
occurs even when core.WriteFile fails; preserve the existing error wrapping and
normal cleanup path.
- Line 39: Replace the probabilistic temporary path generation in the
surrounding helper with an exclusive temporary-file creation API, or create the
generated path using O_CREATE_EXCL before writing. Ensure concurrent writers
cannot reuse an existing .tmp. sibling file, while preserving the subsequent
write and rename behavior.

In `@go/display/webkit/pkg/window/state_test.go`:
- Around line 760-783: Update the concurrent save/read test around the writer
closure and reader loop so both writers signal readiness before reads begin,
ensuring saves overlap the read phase. Propagate each sm.save() error through
the test’s synchronization channel and assert it in the test instead of
discarding it, while preserving the existing JSON corruption check.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cfba034-6dc0-45a8-a00c-e983ec65510c

📥 Commits

Reviewing files that changed from the base of the PR and between 1027de0 and fc1ddd6.

📒 Files selected for processing (4)
  • go/display/webkit/pkg/window/core_helpers.go
  • go/display/webkit/pkg/window/layout.go
  • go/display/webkit/pkg/window/state.go
  • go/display/webkit/pkg/window/state_test.go

Comment thread go/display/webkit/pkg/window/core_helpers.go Outdated
Comment thread go/display/webkit/pkg/window/core_helpers.go Outdated
Comment thread go/display/webkit/pkg/window/state_test.go
…oves overlap

CreateTemp (O_EXCL) replaces the probabilistic random suffix, so two
writers can never share a temp file even in principle; the caller's
mode contract is restored via Chmod before publish, and every failure
branch now removes its temp file. The concurrent test seeds one save
before the storm, reads until BOTH writers finish (overlap guaranteed),
and propagates every save error instead of discarding them.

Co-Authored-By: Virgil <virgil@lethean.io>
@Snider
Snider merged commit 5ac292a into main Aug 8, 2026
2 checks passed
@Snider
Snider deleted the fix/window-state-atomic-write branch August 8, 2026 08:29
Snider added a commit that referenced this pull request Aug 8, 2026
)

The four shells #46 could not touch while PR #44 was in flight, now
adjudicated per case rather than swept.

register_test.go — genuine fake. All three tests asserted that the
string "Register:good" contains "good" and never named Register at all.
Replaced with a real Good/Bad/Ugly triplet that calls it: the returned
constructor yields an OK Result carrying a *Service wired to the given
platform with manager and spec registry initialised (Good); a nil
platform still constructs rather than panicking during Core wiring, long
before any window opens (Bad); and the constructor is reusable, each call
producing an INDEPENDENT service — a shared specs map would let two Cores
overwrite each other's window registrations (Ugly).

window_test.go — mislabelled but real. These tests DO call WithName /
WithTitle / WithURL and assert properly; the ax7Variant lines were dead
weight prepended to working bodies. Removed those two lines per test,
116 in total, leaving every assertion byte-identical.

Receipts:
  coverage  68.3% before, 68.3% after — nothing real was removed
  Register  100.0% (was already covered by service_screen_test.go's
            real use; the deleted shells contributed nothing)
  go test -race -count=2  clean
  vet + gofmt clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant