Skip to content

mcpp-language-server 0.0.8: completion shows while you type beside inline completions, a module being written stays with clangd, and what 0.0.7 left (#34) - #36

Merged
Sunrisepeak merged 53 commits into
mainfrom
release/0.0.8
Oct 1, 2026
Merged

Sunrisepeak merged 53 commits into
mainfrom
release/0.0.8

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

mcpp-language-server 0.0.8: completion that shows up, writing modules with autosave, and what 0.0.7 left (#34).

Plan, evidence and implementation record: .agents/docs/2026-09-30-0.0.8-plan.md.

Completion that shows up (E-1..E-4)

A user reported "no keyword, parameter or variable completion" in VS Code 1.132. Their own diagnostic bundles showed the server was simply not asked: 24 completion requests in 18 minutes of editing, none in the 66 s they reproduced it, while Copilot made 15 inline requests in the same minute. The server answered the 24 in 21 ms at the median.

The cause is a VS Code default. From a release between 1.108 and 1.125, editor.quickSuggestions.other defaults to offWhenInlineCompletions. With an inline-completion provider installed (Copilot is built in), a keystroke waits for the inline completion, and the list is not opened while grey text shows. Otherwise it opens only after 750 ms with the document and cursor unchanged.

  • The fix (WA-VSCODE-002): the extension contributes {other: "on"} as the [cpp] and [c] language default, so the list and the grey text show side by side. Measured on 1.132: other languages keep the editor's default, and a person's [cpp] value wins.
  • The one cost: a value set for every language is overridden. That is said once in the log.
  • Diagnosis: the bundle and the report carry the editor settings that decide it (client.editor), and the server's side of it (requests[*].lastAt, documents).
  • Tests: E2E and a canary.

Writing modules with autosave (M-1..M-4)

A module saved mid-edit does not compile. Containment (RP1.1) then took the module's own file from clangd, together with its importers, until the next save handed it back. Reproduced: in 92 s of writing a module it was doomed 18 times, and 48% of the completions in it were empty.

  • M-1: the unit whose compile failed always keeps clangd. It needs no BMI of its own, so clangd reads it from the editor with its real errors and completion. Its importers are contained as in 0.0.7.
    • A first design also kept the importers with clangd. The ux runs disproved it: every save made clangd rebuild the modules in between, an importer had no diagnostics for minutes, and U15-edits stalled 140-185 s against 0.0.7's 16-35 s.
    • The plan's §8 records both designs.
  • M-2: a completion clangd does not answer gets the file's words, never nothing. This covers an importer of a module that does not compile.
  • M-3: editing the module being prepared is no longer "preparation-stalled", and a new clangd's progress is measured from its own start.
  • K-6: a settled status holds its state through a save's rebuild for 30 s.
  • Tests: new fixture module-edit-autosave and ux scenario U16.
module-edit-autosave 0.0.7 0.0.8
typed file taken from clangd (module-failed on it) yes, repeatedly never
empty importer completions 43 / 47 0 / 47

What 0.0.7 left (#34)

  • P-1: clangd reports one missing module per session. On the first report, the modules whose units it already could not scan are taken together: one replan, one restart instead of four rounds of about 26 s on GalTranslPP. The lookup normalizes Windows paths: clangd names units with backslashes and ... Fixture partial-scan-standins.
  • R-4: the provisional model prepares only std, so the project's modules are no longer built twice. Fixture provisional-no-prime.
  • K-3: the released clangd has no symbols, so a crash keeps LLVM's stack dump and the clangd binary's version and SHA-256 for an upstream report.
  • U-*: mcppls-devtools measure budgets and nightly's ux-budgets job give each scenario's distribution, for setting budgets from measurements.
  • O-6: Open VSX gets ten minutes to list a release.

Part 2: large projects, xmake, CLion and Zed

Plan and record: .agents/docs/2026-10-01-0.0.8-part2-plan.md. It draws on the reporter's bundles (GalTranslPP on Windows, an xmake project) and on experiments with clangd 23.1.

  • C-1: prepared units close at once.
    • Units mcppls opened in clangd to prepare modules were held until all preparation was idle, and clangd re-checks every open file on each save.
    • Under autosave, each pause rebuilt module closures no open file needed (15 units held on GalTranslPP, 70 on xlings).
    • Measured: after didClose, the BMI stays in clangd's module cache and is reused (an importer reopened in 0.35 s vs 1.66 s).
  • C-2: a late completion reaches its word.
    • clangd's answer after the 1 s budget is kept (detached, so it is no timeout) for up to 10 s.
    • Later requests in the same word wait for it and get it, with edits ending at each cursor.
  • K-7: busy without progress.
    • A file queued for 4 min while clangd finished nothing for 3 restarts clangd as a recovery.
    • Seen locally after U16: every core busy for 15+ min, all files queued, and the stuck watch (low CPU) blind to it.
  • xmake: xmake.lua and your own xmake f are followed; nothing is written into the project.
    • X-1: a private xmake run is the source; your compile_commands.json is read only when xmake cannot run.
    • X-2: the private run takes your xmake f platform, architecture, mode, toolchain and options.
    • X-5: a database being rewritten is read again, not reported.
    • X-6: the provisional model no longer says "untrusted".
  • X-3: C++23 for module units whose build names no standard (decided in review: mcppls supports import std by default).
    • Other units that name none get their compiler's own default, e.g. GCC 16's gnu++20.
    • Before: Clang's gnu++17 could not scan GCC 16's std.cc.
  • X-4: a standard library unit that fails to scan is reported.
  • C-4: the report names the slowest files and each file's clangd build times.
  • I-1: include cleaner stays on. Measured correct in module units; a fixture guards it.
  • CLion:
    • Built for 2026.2.3; the Plugin Verifier runs on 2025.2 and 2026.2.3.
    • Headless CLion tests cover the server, diagnostics, import completion and shutdown.
    • One engine per file by default: projects CLion models are CLion's, with a switch.
  • Zed: cargo test for the extension; a real Zed under xvfb, with the recommended settings and with Zed's defaults.
  • K-8, clangd's orphaned builds (found by K-7's incident).
    • Containment closes a failed module's importers in clangd. A build closed mid-flight sometimes keeps spinning with nobody to answer.
    • Three such threads ("TWorker:log.cpp", 250-330 s of CPU each) took all of clangd's workers. ux-xlings then stalled after U16 for up to 30 min; that stall predates part 2 (CI run 36750398125).
    • A worker at a full core for 30 s on a file clangd has not reported building meanwhile now restarts clangd. U16 allows that one recovery restart.

Verification

  • Tests: unit tests (server, the five workspace members, the VS Code extension, the Zed extension's cargo test) pass. The CLion tests run in a headless CLion 2026.2.3.
  • CI: conformance on the four platforms (the xmake fixtures on Linux), the editors' end-to-end suites, ux on mcpp and xlings, and release checks (real Zed, CLion plugin verifier, install/remove, performance, stability). Green on run 36801524057 (0896c3c); the commit after it only dates the changelog and records the run.
  • Cross-validated on [temporary] CI probe: mcpp-language-server on GalTranslPP (issue 23) GalTranslPP#1 (Windows, 4 cores; probe 36774111298 on this PR's payload), against part 1's probe:
  • Self-review: a review of part 2's server changes found that late completions were keyed without the typed prefix and never expired; fixed.
  • Budgets set from measurements (each with a budget-why in the fixture):
    • U16 allows K-8's one recovery restart and the few requests it cuts short;
    • xlings' U16 engine share is 0.15 (0.20-0.31 measured with that restart);
    • U7a's republish budget is 33 s (part 2 measured 6.9-30.6 s; part 1 measured 31 s).

…nline completion is installed

Since a VS Code release between 1.108 and 1.125, `editor.quickSuggestions.other` defaults to
`offWhenInlineCompletions`: with an inline-completion provider (Copilot is built into VS Code 1.132)
a keystroke first waits for it, the completion list is not opened at all while it shows grey text,
and otherwise only after it answered or 750 ms passed with the document and cursor unchanged. On the
hello111 project that was reported as "no keyword, parameter or variable completion": the server
received 24 completion requests in 18 minutes of editing, none while the person reproduced it, and
answered each in 21 ms at the median.

The extension contributes `{other: "on", comments: "off", strings: "off"}` as the language default
of `[cpp]` and `[c]` (WA-VSCODE-002): the list and the inline completion side by side, as before that
release; other languages keep the editor's own default. A language default outranks a setting that
names no language, so whoever set `editor.quickSuggestions` for every language is told once, in the
log, how to keep theirs. The diagnostic bundle and the report carry the editor settings that decide
whether completion shows (the value and where it comes from, inline suggestions, autosave, the
inline-completion extensions), and a canary fails once VS Code's own default no longer waits.
…rds, not nothing

A file clangd does not serve -- set aside, taken by containment, or clangd backing off after
crashes -- was answered by mcppls's own engine alone, which knows module syntax and nothing else:
an empty list for `i` or `a_`, in under 2 ms. Such a completion, and one clangd answered with an
error, now gets what the R-7 budget answer has: the file's words nearest first and the keywords, as
an incomplete list the editor asks again for -- never on an import line, where only module names
belong. The report counts them (`completion.wordsWithoutCore`).
…model's stand-ins come in one restart

M-1: with autosave, a module interface saved mid-edit does not compile, and containment (RP1.1)
took the module's own file and every importer from clangd: didClose, one "module did not compile"
diagnostic with no position, completion answered by mcppls's engine -- and the next save handed them
back, so they flipped every few seconds (92 s of writing a module: doomed 18 times, 48% of the
completions in it empty). A module that fails while a source of its closure is open and was edited
within two minutes is now work in progress: its files stay with clangd, which reads the one being
edited from its buffer and gives an importer its own locals and keywords with an error on the import.
Two minutes after the last edit, if what failed is still on disk, containment takes it as before.
Completions clangd does not answer are wired to the file's words (the previous commit).

M-3: an edit to a source a module being prepared is built from counts as progress, so writing that
module is no longer reported as "preparation-stalled ... cannot recover by itself" with an automatic
bundle; the report lists the prime units running, for how long, and clangd's status of each.

P-1: clangd reports the modules it cannot find one per session -- it stops at the first import it
cannot build -- so a partial model brought its stand-ins in one clangd restart each (GalTranslPP: four
rounds of about 26 s). By then clangd has logged every unit it could not scan; all their modules are
taken together, for one replan and one restart.

R-4: on the provisional model (no cache, the build tool still to answer) only the standard library is
prepared; the project's modules wait for the build tool's commands instead of being built twice, and
Windows' clangd no longer crashes on the provisional ones.

K-3: a crash keeps LLVM's stack dump as clangd printed it and which clangd it was (version, sha256),
for an upstream report. K-6: a settled workspace turns preparing again only when a rebuild lasts
30 s. E-3: the report says when each method was last answered and how much was edited.
… and Open VSX gets ten minutes to list a release

The ux budgets were widened over three rounds of the 0.0.7 pull request because the same runner type
differed by up to 1.9x; setting them from what the runners measure needs the numbers over many runs.
`mcppls-devtools measure budgets <dir>` reads every --measure file under a directory and prints, for
each check and each number of its measure, the runs, median, 95th percentile, largest value and 1.3
times the 95th percentile; nightly's new ux-budgets job runs it over the night's three rounds.

publish-openvsx.yml waited three minutes for Open VSX to list each VSIX, and 0.0.7's darwin-arm64
took longer: the check failed although the file it served was right. It waits ten.
…riting modules with autosave, and the plan

The plan (.agents/docs/2026-09-30-0.0.8-plan.md) with its decisions (Q1-Q5 as recommended), its
second revision of K-3 (the released clangd has no symbols: a crash keeps its stack dump and which
clangd it was) and of P-1 (clangd reports one missing module per session, so the modules whose units
it could not scan are taken together), and the split of the work by architecture, stability,
simplicity, experience, compatibility, platforms, consistency and upgrade. The design record gains
UD5 (the completion list in VS Code), RD15 (containment is for a broken module, not one being
written) and RD16 (the provisional model prepares std only; stand-ins together). Troubleshooting,
editors and settings, in English and Chinese, say why the list stayed closed and what the bundle
shows; the changelogs and every editor plugin carry 0.0.8.
…status holds only its state

`Json words { x }` made a one-element array of the words, so a completion clangd did not answer went
out with an array among its items (failure-at-base's stress check stopped on it) and was never
empty. The settled-preparing hold (K-6) took a ready status from before clangd was serving for a
settled workspace, and held everything back while it lasted, the issue failure-at-base waits for
included: only a ready or degraded state while the core engine serves counts now, and only the state
is held. The engine's two lookups of a failing module's edited sources are one.
…eep an importer open and budget dooms, module-failed diagnostics and stalls

Fix plan 0.0.8 M-4. The typing kind saved on a fixed interval only, so it could not reproduce files.autoSave afterDelay, where a half-typed statement is written to disk each time the person pauses. autosave-idle-ms saves once that long has passed since the last key and the buffer differs from what was last saved. importer opens a second file with a probe line inserted in its buffer, asks for a completion at the end of that line beside each of the typed file's, and keeps its latencies and empty answers apart. The new budgets doomed, moduleFailedDiagnostics, stalled, importerP95 and importerEmpty are measured from the report's event totals, a per-uri count of publishDiagnostics carrying code module-failed, and the status samples, and are reported with the existing fields.
…itten is taken from clangd

A module interface written with autosave afterDelay, its importer open with a probe line in main(), 30 s at 10 Hz. Against 0.0.7 it fails on doomed files, module-failed diagnostics and empty importer completions; a server that keeps a module being edited in clangd passes with all of them at 0. The fixture runs in the pull request conformance job on linux, darwin, arm64 and win32 beside typing-autosave.
…mcpp and ux-xlings, and the typing keys are documented

U16 types functions with half statements into a module imported by a few modules (mcpp.pack.digest with mcpp.pack.prebuilt open in mcpp; xlings.core.console with xlings.core.log open in xlings) for 90 s with autosave a second after the last key. Nothing may be doomed, no module-failed diagnostic published, no status sample say preparation-stalled, clangd restarted, or an importer completion be empty; typed-file p95 1.1 s, importer p95 1.5 s, engine share 0.75. conformance/README.md lists the fixture, the typing kind's new keys and budgets and U16 in the edits stage.
…l while stand-ins arrive one restart at a time and project modules are prepared from the provisional model

Fix plan 0.0.8 P-1 and R-4. partial-scan-standins has three module interfaces whose scans fail at different times (a generated chain of headers before a missing one), so clangd names one module it cannot find per session: 0.0.7 restarts clangd twice, for a and b and then for c, and the fixture allows one. provisional-no-prime delays the mock producer by eight seconds (the delayed-producer prepare step, a shell script around the mock) and reads cxxModules/report six seconds in: 0.0.7 already wants three units for preparation, std, std.compat and the project's module, where the fixture allows two. The runner gains the report expectations at-most, max-matches and min-matches and the check key not-before-since-start, documented in conformance/README.md.
…settings tables instead of sitting inside them

test_settings holds docs/30-settings.md and its Chinese mirror to the renderer's output between the
settings markers, byte for byte; the paragraph about WA-VSCODE-002 had been put inside.
…ts clangd names with backslashes are found

M-1 kept a module that fails while it is being written with clangd, but not what containment also does
for it: give up the preparation of the modules that cannot build now. A prime unit waiting on the
module being written kept the primer busy and, with M-3 counting the edits as progress, the workspace
"preparing" with nothing to show for it -- 140 s on ux-mcpp and 185 s on ux-xlings (U15-edits, over
its 60 s). Their prime units are closed and resolved, as doomed modules' are; clangd builds them for
the files that need them once they compile again.

P-1 looked the units clangd could not scan up by their path key, but clangd names a unit as its
command line did -- `D:\a\...\Updater\..\3rdParty\3rdModule\boost.ixx` on Windows -- and the plan's
units are normalized: on GalTranslPP nothing matched and the stand-ins came one restart at a time
again. Both sides are normalized.
…its budget already allows

The restart a half-typed import on disk makes clangd need waits for a 3 s pause (R-8), which is when
the typing ends and the refresh is measured: 14.5 s on the 0.0.7 merge, 16.3 s on this pull request,
under 6 s in rounds without it. The fixture's description records it.
…rters are contained as before

The first M-1 kept a failing module's whole closure with clangd while a source of it was being
edited. The pull request's ux runs and xlings locally disproved it: with the importer open, every save
of the module made clangd build the modules between again, the importer had no diagnostics for minutes
after the typing stopped, was set aside and clangd restarted for it, and the status stayed preparing
140-185 s (U15-edits; 0.0.7 had 16-35 s).

What the person writing a module needs is that module itself: it needs no BMI of its own, so clangd
reads it from the editor with its real errors and completion at no cost to anything else. The unit
whose compile failed is left out of the doomed files; everything else containment does is as in 0.0.7,
and the importers' completion has the file's words (M-2). The deferral, its two-minute review and the
guard's editing_until are gone. A new clangd's preparation progress is measured from its own start: the
last clangd's made a restart report "preparation-stalled ... cannot recover" two seconds in.
…led diagnostics, and the docs follow M-1's third revision

`moduleFailedDiagnostics` counts the typed file's only (a file taken from clangd gets one); the
importer's are reported apart, since an importer of a module that does not compile is contained by
design. U16 and module-edit-autosave drop the `doomed` budget and U16's engine share is 0.4: the
importer's half of the completions is mcppls's while the module does not compile. The plan, the design
record, the changelogs and troubleshooting (English and Chinese) describe the revision and why.
… not stop clangd's crashes on GalTranslPP

The cross-validation (Sunrisepeak/GalTranslPP#1, run 36738002085) shows clangd still crashing four
times while the provisional model serves GalTranslPP, as with 0.0.7: in the opened files' own builds,
not in preparation. The comment, the changelog and the plan say what R-4 does and what stays open
(#34-1).
…the word it was asked in, and GCC's own standard is read

Part 2 of the 0.0.8 plan (.agents/docs/2026-10-01-0.0.8-part2-plan.md), from the GalTranslPP and xmake bundles.

C-1: a prime unit is closed as soon as its module is prepared. They were held open until all preparation was idle, and
clangd re-checks every open file on didSave and rebuilds the modules of those whose imports changed, so each autosave
rebuilt module closures no open file needed, on the workers the typed file waited for (15 held on GalTranslPP, 70 on
ux-xlings). Measured with clangd 23.1: after didClose the BMI stays in clangd's module cache and a reopened importer is
diagnosed in 0.35 s against 1.66 s the first time.

C-2: a completion clangd did not answer in its 1 s budget is no longer cancelled. It keeps working for up to 10 s, the
requests the same word makes meanwhile wait for it, and its answer goes to them, its edits ended at each one's cursor;
a request in another word cancels it. Cancelled, clangd's answer never reached anyone: the next keystroke queued behind
the same rebuild and missed its budget too (GalTranslPP: 25 of 48 completions answered with the file's words).

C-4: the report names the ten slowest files by request (slowestFiles) and what building each file cost clangd, from its
log (engines[].details.buildTimes: preambles, imported modules, AST builds).

X-3: a GCC command naming no standard is read with GCC's own default where Clang's differs (GCC 16: gnu++20; GCC 15 C:
gnu23). xmake writes no -std when xmake.lua sets no language; Clang's gnu++17 could not scan GCC 16's std.cc (20
errors) while the build compiled it. Module units read below C++20 are said once (module-standard-too-old).

X-4: a scan failure of the standard library's module units, or of a unit outside the workspace, is the command's, never
a half-typed file's: it is reported (module-scan-failed) instead of ignored.

I-1: clangd's include cleaner stays on. A new fixture, include-cleaner-modules, with a check kind diagnostic-code-lines,
holds that it reports only unused headers in a module interface, an implementation unit and an importer.
…runs

Where the server comes from (mcppls on PATH, else the installed payload, else an error that names
the fix) moves to resolve.rs, which names no Zed type, so it compiles for the host as well as for
wasm32-wasip2. lib.rs maps Zed's platform onto it. Six tests cover PATH winning, the payload when
PATH has none, XDG_DATA_HOME, the error text, and the Windows and macOS payload paths.
…commended settings and with Zed's defaults

editors/zed/tests/smoke.sh installs the extension the way release-checks does, starts the pinned
Zed (ZED_VERSION in versions.env, sha256-checked) on a copy of the inferred fixture with its own
HOME and XDG directories, and reads the conversation from a copy kept by a mcppls script first on
PATH: within 60 s Zed initialized the server as "Zed", sent the file and got diagnostics for it.
The script is also how the server gets --log-level debug, which the extension cannot pass. With
the recommended settings Zed starts no clangd; with its defaults it does, beside mcppls, and that
both ran is recorded. Quitting Zed must leave no mcppls process.
… change can reach it

zed-unit runs the extension's tests and its wasm build on every pull request. zed-smoke.yml is the
real-Zed smoke as a reusable workflow on the run's Linux payload; ci.yml calls it for a pull request
that touches editors/zed, src/lsp, src/orchestrator or devtools or that belongs to a release/*
branch (not again when release-checks is about to), nightly.yml calls it every night, and
release-checks.yml makes it part of the release candidate.
The Zed README and the editors pages (English and Chinese) number the install as the extension and
then the settings that put mcppls first and clangd off, and say what happens without the second
step. The README's status section describes how the extension is tested.
…s written, not parsed for every report

The report parsed clangd's whole log ring for engines[].details.buildTimes, and held the event loop 2.3 s
for each cxxModules/report on ux-xlings. The times are now added up line by line on the thread that
reads clangd's standard error, and a report copies the sums.
…Verifier checks it on 2025.2 and 2026.2.3

platformVersion moves to 2026.2.3 (build 262.10968.117), the IntelliJ Platform Gradle Plugin to
2.19.0 and Kotlin to 2.4.0, which the 2026.2 jars' metadata needs. sinceBuild stays 252.
pluginVerification.ides lists CLion 2025.2 and 2026.2.3, so `gradle verifyPlugin` fails on an
incompatible change in either.

Built against 2026.2.3, Kotlin copied the renamed LSP interface's default methods into the
provider as calls to super, and the verifier found four NoSuchMethodErrors on 2025.2. Compiling
with -jvm-default=no-compatibility leaves them out; both builds verify as compatible. The test
task and its dependencies (the platform test framework, JUnit 4) are declared here too; the tests
follow.
…ne, unless Settings | Tools | mcppls says otherwise

The plugin used to start mcppls for every C and C++ file, so a CMake project got two lists of
completions and two sets of diagnostics. Now a file has one engine. A project CLion models (a loaded
CMake, compilation database, Makefile or open-folder workspace, asked of CidrWorkspaceManager, or a
CMakeLists.txt at the root) is CLion's and mcppls does not start for it; every other project is
mcppls's.

The workspace classes live in CLion's C/C++ plugin, so the question is put through an extension point
that mcppls-cidr.xml fills only when that plugin is there (an optional dependency): where the API is
missing or has changed the plugin still loads and the CMakeLists.txt check alone decides.

Settings | Tools | mcppls has one checkbox, "Also for projects CLion models" (an application-level
PersistentStateComponent, off by default). With it on both engines answer, and the plugin says so
once per project in a balloon.
…s the verifier on every pull request and that test where it matters

The test opens a real directory project with the IntelliJ Platform test framework and starts the
mcppls found first on the PATH (CI puts the payload it built there). On an mcpp package it asserts
the server Running within a minute, the diagnostic for a wrong import, the module name
`import hel` completes, and the process gone once the project closes. On a CMake project it asserts
no server by default and, with the setting on, a server and one notice. What CLion's own engine
answers for the same files is printed, not asserted, and a separate check confirms the optional
workspace probe registers inside CLion.

ci.yml gains clion-verify (verifyPlugin, every pull request) and clion-e2e (the test, when the
change touches editors/clion, src/lsp, src/orchestrator or tools/devtools, on release branches, and
on every run that is not a pull request, which covers the nightly and the release's CI). The CLion
downloads are cached by version. The release checks' install test names CLion2026.2. The CLion
README and docs/10-editors.md (and the Chinese mirror) drop 'not exercised in a running CLion' and
describe the one-engine default, the switch and the supported versions.
…te run follows the user's own xmake f

X-1 and X-2 of the 0.0.8 part 2 plan. When the workspace is trusted, mcppls.buildTool is not off and xmake is on PATH, the private xmake run is the only source: a compile_commands.json the user generated follows their last xmake project, not their xmake.lua, and two sources taking turns restart clangd (E1, the late set_languages that never reached the model). The file is read only when xmake cannot run, or when a first private run fails and none ever succeeded (a half-typed xmake.lua must not replace an xmake model with an older file's); it is watched then, and a notice says once that it is older than an xmake.lua or the user's xmake.conf, with the one command that updates it. A failure that needs a download stays a download offer.

The project's .xmake/<plat>/<arch>/xmake.conf (the newest) is read as text, only top-level strings and true/false, never executed, and the private xmake f is given plat, arch, mode and the rest of what the user chose, minus xmake's own keys (E5: xmake f resets what it is not given, so a debug project was described as release). An option xmake refuses (exit 255, Invalid option) is retried once with the standard ones only and a notice names what was left out. The configuration key includes the conf's size and time, and **/xmake.lua and the conf are watched. Unit tests cover the parser on a recorded xmake.conf, the arguments, exclusions and the retry allowlist, the key and the staleness decision.
…reported, and the provisional model stops saying the workspace is untrusted

X-5: a reload that finds compdb-invalid or database-invalid while a file it reads (the failing model's or the last model's watched json, or the file the error names) was written in the last two seconds is repeated a second later, up to five times, with no warning and no stale issue; the last model stays meanwhile, and a journal event model-reread records each try. After that, or once the file is two seconds old, it is reported as before.

X-6: the provisional model loaded from scanned sources is untrusted only so that no program runs; it drops the untrusted-workspace issue, and the trust state is reported by the orchestrator alone.
…checks they need, run in the Linux job with xmake from xlings

X-7 of the 0.0.8 part 2 plan: xmake-basic, xmake-late-config (a stale root compile_commands.json, then set_languages in xmake.lua: the engine follows within 15 s, nothing reported, the project directory unchanged), xmake-user-mode (.xmake/linux/x86_64/xmake.conf with mode=debug and a project option), xmake-no-standard (passes once X-3 lands) and compdb-midwrite (a truncated database completed 300 ms later, and by a 2.5 s writer). New runner checks: engine-command (the command clangd was given, from the engine database), status-never (no status named an issue), write-midway (a database cut and completed); prepare kinds xmake-stale-compdb and compdb-midwrite. max-matches and min-matches now count when equals is beside them, as the README says. CI pins xmake in .github/versions.env and installs it with GCC 16 from xlings for the jobs that run an xmake fixture.
…and follows xmake.lua and the user's xmake f, and compile_commands.json is read only when xmake cannot run
…s C++23, and a database being written is read again for longer

X-3, second revision (decided 2026-10-01): mcppls supports `import std` by default, so a module unit -- one that
imports, provides or belongs to a module, std's own unit included -- whose build names no standard is read as C++23
(gnu++23; c++23 for an MSVC target), whatever the compiler's default: Clang's gnu++17 has no modules at all. A standard
any module unit names is followed instead; plan.standardAssumed says when mcppls chose. GCC's own default is given only
to plain and C units now. xmake-no-standard is the reporter's project again: main.cpp alone imports std (with a module
file xmake itself adds -std=c++20, which is followed), and std::println, which C++20 lacks, is found.

X-5: a database written in the last 10 s (was 2 s) is read again: on a loaded machine the load itself came more than
2 s after a writer that took 2.5 s. The runner's write-midway waited for quiet between its two writes, and a server
reading the file every second kept it from ever being quiet; it waits a fixed time now.
… the include-cleaner fixture allows fewer reports

C-2: a completion clangd keeps working on after mcppls answered it is detached (Engine::detach): nobody waits for it,
so when its time runs out it is cancelled quietly -- no request-timeout, no "did not answer", no reason to set its file
aside, and it no longer counts as the oldest unanswered request when clangd is judged stuck.

I-1: clangd with the Windows kit reports none of the unused headers the fixture has; diagnostic-code-lines takes
"subset", and the interface and plain files may be reported on fewer lines, never on others. The importer, the
implementation unit and the member access still must have none.
…thing for minutes is restarted

K-7 (plan 0.0.8 part 2). A file clangd has queued is waiting for a worker (K-5) and is not set aside for it -- but a
clangd that keeps its workers busy without finishing anything never frees one: after U16's module autosave on
ux-xlings it kept four cores busy for more than fifteen minutes with every open file "queued", no diagnostics, no
answers, and the stuck watch, which looks for next to no CPU, did not see it; only a restart that happened to come
ended it. A file queued for four minutes while clangd finished nothing for three (no diagnostics for any file, no
answer, no prepared module, no background unit) now restarts clangd as a recovery, with an incident carrying its log.
…te completions, prepared units, busy-without-progress, xmake, C++23 for unstated module standards, CLion and Zed
…or five seconds

Found in review. The late answer's key was the word's line and the text before it, so a word typed back
(`std::ve` answered, then a backspace to `std::v`) got the list clangd had narrowed to `ve`, and a return to
the same word minutes later got a list from before the file changed -- served without asking clangd, and
never cleared. The key now carries what of the word was typed: an answer or a pending request serves a
request only when the word has been typed on from it, a late answer is kept five seconds, and a word typed
back cancels the pending request as another word does. K-7 is said once per clangd, not at every recheck
while a restart is backed off. The conformance README describes xmake-no-standard as it now is.
CI run 36773996351 counted the notices one event turn after the server was running and found none: the
notice goes out through the message bus, a few turns later. The test now waits for it, then gives a
second one three seconds to show before asserting there is exactly one.
…asking it for --version, which it does not take
…tage uploads the server's logs

CI run 36773996351 showed ux-xlings stuck after U16 for half an hour, as run 36750398125 of part 1 did,
and K-7 never restarted clangd: a background unit set aside every two minutes was closed, clangd cleared
its diagnostics, and that publish counted as progress; so would an error for a cancelled request. Progress
is now diagnostics for a file the editor has open (not one set aside or excluded), an answer with a result,
a prepared module or a built background unit. The ux job uploads the server's logs and incidents when a
stage fails.
…re the VFS lists it

CI run 36782178214: the first files of a project just opened reached fileOpened before the VFS had the
root's children, so a CMake project looked unmodelled -- by default mcppls would start where CLion's own
engine answers, and with the switch on nobody was told that both engines do. The root is looked at on
disk as well.
…is not building restarts clangd

K-8 (plan 0.0.8 part 2). The incident K-7 wrote on CI run 36782178214 named the ux-xlings stall: three
threads "TWorker:log.cpp", each at a full core with 250-330 s of CPU -- all three workers, on log.cpp, the
importer of the module U16 rewrites, which containment closes in clangd at each autosave while its build
runs. A build clangd let go of and never stopped has nobody to answer; every open file stayed queued, and
neither the stuck watch (next to no CPU) nor the spin watch (a file clangd reports building) could see it.
clangd's worker threads are now sampled every 15 s (Linux: per-thread CPU); one at a full core for a minute
on a file clangd is not building restarts clangd, once, with an incident. worker_file reads the file from
the thread's name as Linux truncates it ("TWorker:log.cpp", "rker:doctor.cpp").
…id it was building that file meanwhile, after half a minute

A worker on the file being typed is busy for minutes at a stretch, and the moment K-8 decided could fall
between two rebuilds, when clangd says the file is idle. An orphan is now a thread at a full core for 30 s
(sampled every 10 s) on a file clangd has not reported building at any time since -- every file's reports
count, prime and background units' too -- so a real build is never taken for one, and the recovery comes
within the scenario that caused it. CI run 36787135201: ux-xlings no longer stalls after U16 (U7a, U8-config
and U15-edits pass), and the U16 restarts were K-8's, on the importers containment closes (log.cpp,
prebuilt.cppm). U16 in both ux fixtures now allows the one recovery restart and the few requests it cuts
short; its completion budgets stay. The Zed smoke waits for a debug line in any of the server's logs.
…was building, and the Zed smoke no longer names a removed variable

CI run 36792920474 restarted clangd in ux-xlings' warm stage for "eWorker:cli.cpp": cli.cpp's preamble, a long
module closure, built for 31 s, and clangd says so once, at the start -- before the thread counted as busy, so
"reported building since" missed it. A worker is now an orphan only if clangd's last word on its file is not
"building" and it said nothing of the kind since the worker got busy; a file mcppls closes in clangd loses its
last word, so a build left spinning after a close is still caught. The Zed smoke printed a variable an earlier
change removed and stopped there under `set -u`; both settings pass on Zed 1.22.0 here.
…a's republish budget comes from measurements

The switch-on CLion test waited for the balloon on the project's message bus, which headless CLion did not
always deliver within 30 s (CI runs 36782178214, 36797333606) though the plugin had decided to tell; it now
waits for that decision, once per project, and holds the balloons to at most one. U7a's budget for the
importers' diagnostics was 20 s; over six CI runs of part 2 ux-mcpp measured 6.9-25.2 s (part 1: 31 s), so it
is 1.3 times the 95th percentile, 33 s, the rule `measure budgets` gives, in both ux fixtures.
…overy restart comes

With K-8's recovery restart in xlings' U16, clangd answered 0.20-0.31 of the completions (CI runs of part 2);
the budget is 0.15, the lowest over 1.3. Completion p95, the importer's p95 and no empty completion keep their
budgets; ux-mcpp's U16, which needs no restart, keeps 0.4.
@Sunrisepeak
Sunrisepeak merged commit 9d9f034 into main Oct 1, 2026
61 checks passed
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