Skip to content

Fix the red master and nightly CI: a ui use-after-free, two glaze brace-init bugs, and eight CI causes - #1037

Merged
Yaraslaut merged 10 commits into
masterfrom
fix/nightly-and-master-ci
Oct 22, 2026
Merged

Yaraslaut merged 10 commits into
masterfrom
fix/nightly-and-master-ci

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Master CI has been red on most legs since #1004, and two nightly checks have failed three nights running (#1005, #1006). This branch fixes every cause found, including three library defects.

Library defects (include/morph, src)

  • Use-after-free in ui::detail::Mounter. If a tab's page fails to mount, the failure is reported through onSelect, and that handler may destroy the Mounted. underOverlay's Restore then wrote into the freed Mounter. Valgrind reported Invalid write of size 8 … Restore::~Restore() (mount.hpp:244), reproduced locally on master. Only the page's mount now runs under the overlay, so the overlay is back in place before the failure is reported.
  • Glaze brace-init builds one-element arrays. Json{std::string{text}} gave ["text"], so a string-typed field component reported its change as an array. Json{Json::object_t{}} gave [{}], so an MCP tools/call without arguments was refused. Both now use parentheses. Reverting either fix makes its new test fail.
  • qmllint warnings in src/qt_quick.
    • MorphStack read Layout through children (typed QtObject), which Qt 6.8 rejects.
    • MorphPanel's divider set height on an item a layout manages.
    • Verified with Qt 6.8.1's own qmllint (the CI version): the original files reproduce CI's exact warnings, and the fixed files give none.

CI, tests and examples

  • examples/client: saveGrab's parameter and kGrabSettle were unused without Qt Quick. That -Werror broke the asan, tsan and ubsan legs before any test ran, which is also why ASan never saw the use-after-free.
  • test_check_install_export.sh: the dropped-detail-headers mutation is caught, but now on reactive/detail/graph.hpp, not completion_awaiter.hpp. Updated the needle. The installed tree itself was fine.
  • test_sign_in (Windows): the temp path was spliced into JSON with backslashes. It now uses generic_string().
  • test_qt_quick_table: the tests hardcoded a unit of 8 px, but the table pads by Theme.unitWidth, the font's average character width (6.08 px on CI). Also the const references clang-tidy asked for.
  • cmake/tsan.supp: added race:std::logic_error::~logic_error, the logic_error form of the existing exception_ptr false-positive entries. A race-free standalone control reproduces the report on Linux/libstdc++. A planted race is still reported 10/10. Closes core: TSan reports a race between a Completion's exception being freed on the pool and read in onError on the main thread #985.
  • test_push_net: the connect-and-reconnect test gets a 10 s budget. It timed out at 2 s under Valgrind on two of three nights.
  • Coverage:
    • include/morph/ui fell to 91.37% against its 92% floor. New tests cover inline bytes and base64, field-component change typing, map-held rows, sizing validation and localized explorer titles. It now measures 92.16%.
    • include/morph/client gets a floor (4/4 arms).
    • Six moved allowlist hints in rational.hpp and document.hpp are updated.

Verification (local, Arch, GCC 16 / clang 23, Qt 6.12)

  • gcc-debug: full ctest passed, 3991/3991, before the last two commits. Since then the affected [interpreter], [agent] and [explorer] tests pass there, and the coverage run below passes on the final tree. morph_tests [ui] under Valgrind: 0 errors.
  • clang-coverage with CI's configure: 4131/4131 tests pass, and scripts/coverage.sh passes its gate.
  • scripts/test_check_install_export.sh: all checks pass.
  • examples/client/main.cpp compiles with -Werror, with Qt Quick both on and off.
  • clang-format 22.1.8 is clean on every changed file. clang-tidy over the changed lines is clean (local 23, not CI's 22).
  • Not verified locally: Windows, and the Valgrind and TSan nightly legs end to end. ci: nightly Valgrind memcheck failed #1005 and ci: nightly Linux / all optional features failed #1006 close themselves when their nightly jobs pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw

Yaraslaut and others added 10 commits October 22, 2026 20:40
… Mounter

A failed page is reported through onSelect, which may destroy the Mounted
that owns the Mounter. The overlay was put back by a destructor that ran
after that report, writing into freed memory (Valgrind: invalid write in
underOverlay's Restore). Only the page's mount now runs under the overlay,
so the overlay is back before the failure is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
saveGrab's parameter and kGrabSettle are unused when the client has no Qt
Quick frontend, which -Werror rejects, breaking every sanitizer leg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
… reaches first

With every detail/ header dropped, the consumer now stops at
reactive/detail/graph.hpp (through client/shell.hpp), not
core/detail/completion_awaiter.hpp, so the mutation was caught for the
right reason but reported as the wrong one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
A Windows temp path spliced into the document's JSON carried backslashes
that the loader reads as escapes, so the document failed to load on both
Windows legs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
MorphStack read Layout through children typed QtObject, which Qt 6.8's
qmllint rejects as an attached type on a non-Item; the child is now cast to
Item. MorphPanel's divider set height on an item a layout manages, which a
layout overrides; it now sets implicitHeight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
… pixels

The table pads a content column by two units of the theme font's average
character width; the tests assumed 8 pixels, so they failed wherever the
font's average width is below 8 (6.08 on CI's runner). Also the const
references clang-tidy asked for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
…positive

Same mechanism as the runtime_error entry: the last reference to a shared
exception is dropped on another thread than the one that read what(),
ordered only by libstdc++'s uninstrumented refcounts. A race-free
standalone control reproduces the report on Linux with libstdc++.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
…meet

Each step waits on a real connect and handshake; under Valgrind memcheck
that sometimes outran the default two seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
…are no longer one-element arrays

glaze's generic JSON prefers its initializer-list constructor under braces,
so Json{std::string{text}} built ["text"] and Json{Json::object_t{}} built
[{}]. A string-typed field component therefore reported its change as an
array, and an MCP tools/call that omitted `arguments` was refused as "the
arguments are not an object". Both now construct with parentheses.

Also: a changeWire refusal for an `exact` prop reads "an exact".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
…, follow moved hints

include/morph/ui had dropped to 91.37% of branch arms, under its 92% floor:
inline bytes and base64 over the whole alphabet, a field component's change
read as its prop's type, rows held as a map's values, sizing validation and
a localized screen title had no test. With them it measures 92.16%.

include/morph/client was in the report with no floor; it now has one, after
testing what a source answers before its first bundle (4 of 4 arms).

Six allowlist line hints in rational.hpp and document.hpp had moved with
their lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw9JacbWXaAPaoKGVy6ZHw
@Yaraslaut Yaraslaut added the bug Something isn't working label Oct 22, 2026
@Yaraslaut
Yaraslaut merged commit f13c29a into master Oct 22, 2026
18 of 19 checks passed
@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: TSan reports a race between a Completion's exception being freed on the pool and read in onError on the main thread

1 participant