Version: mcpp 2026.9.4.3 (release binary, linux-x86_64), ninja 1.12.1 from the xim payload. Line numbers are against main at 1e2137b.
What a user observes. $MCPP_HOME/config.toml is written by mcpp itself with a [build] table holding default_jobs = 0 (the template is src/config.cppm:356-358, written for a fresh home by write_default_config_toml). Setting it to 4 changes nothing: mcpp build on a workspace of seven members launches the backend as ninja -C <output dir> (with --quiet, or -v when verbose) and no -j, so ninja runs its own default, which ninja --help reports as 10 on this 8-core machine. Quoting the value (default_jobs = "4") changes nothing either, and by the value_or(0) at the parse site an absent key and 0 are the same. MCPP_JOBS=4 mcpp build on the same tree launches ninja … -j4, and MCPP_JOBS=2 launches -j2 (--jobs/-j is the same channel, src/cli.cppm:188-191 sets MCPP_JOBS), so the observation method, reading the ninja argv with pgrep -af while a rebuild runs, does see the flag whenever there is one.
Where it goes, read from the source. src/config.cppm:517 parses the key into cfg.defaultJobs, declared at :96 under the comment "From config.toml [build]", and those two lines are the only mentions of the field in the repository: it has no consumer. (The key string itself also appears in four e2e fixtures and one CI action, each of them another copy of the generated file.) resolve_jobs in src/build/schedule/policy.cppm:242-265 reads MCPP_JOBS and the manifest's [build] jobs and returns 0 when neither is set; the precedence comment at :127-129 lists exactly those two before "0, meaning say nothing and leave the backend's own default". That 0 travels through schedule::decide into BuildOptions::parallelJobs (src/build/execute.cppm:774), and src/build/ninja_backend.cppm:2846-2847 omits -j when it is 0. The global config never enters the chain. default_backend in the same table has the same shape: parsed at :518 into defaultBackend, never read.
Why it matters more than a dead key usually does. The precedence comment itself makes the case (policy.cppm:131-136): a single module compile peaks at 0.5–1.0 GB, and on a high-core, modest-RAM machine the backend default swaps. That is a property of the machine, not of a project, and the two knobs that do work are both the wrong shape for it. MCPP_JOBS (or --jobs) has to be said on every invocation, or baked into every wrapper and every recipe that calls mcpp. [build] jobs is per-package, and [workspace.build] rejects it ("has no key 'jobs' (or it is not inheritable)", the known-key list at modules/manifest/src/toml.cppm:2652-2657, which is a reasonable rule), so a seven-member workspace would carry the number seven times and commit a machine property into the repository. The per-machine config is the natural home for it, mcpp already plants the key there, and it is the one place that does nothing. The machine this was found on has 8 cores and 15 GiB; ten concurrent module compiles at the sizes the comment quotes is the arithmetic that makes it unusable while a cold build runs.
What would answer it. Either give resolve_jobs the global value as a parameter and place it between the manifest and the backend default — MCPP_JOBS > [build] jobs > global default_jobs > 0 — so the existing comment stays true with one more step and "0 = say nothing" is unchanged for everyone who has not touched the key. A parameter rather than a config import, because resolve_jobs deliberately carries no dependency beyond the manifest today (:138-139), and because it has a second caller at src/build/execute.cppm:2165 for test-runner concurrency, where 0 falls back to hardware_concurrency() rather than to the backend; wiring the key in would cap that as well, which is probably what someone setting a machine-wide number wants, but it is a second behaviour to name. Or drop default_jobs (and default_backend, if it is likewise unread) from the template and the parser so the generated file stops promising them.
Version: mcpp 2026.9.4.3 (release binary, linux-x86_64), ninja 1.12.1 from the xim payload. Line numbers are against
mainat 1e2137b.What a user observes.
$MCPP_HOME/config.tomlis written by mcpp itself with a[build]table holdingdefault_jobs = 0(the template issrc/config.cppm:356-358, written for a fresh home bywrite_default_config_toml). Setting it to 4 changes nothing:mcpp buildon a workspace of seven members launches the backend asninja -C <output dir>(with--quiet, or-vwhen verbose) and no-j, so ninja runs its own default, whichninja --helpreports as 10 on this 8-core machine. Quoting the value (default_jobs = "4") changes nothing either, and by thevalue_or(0)at the parse site an absent key and 0 are the same.MCPP_JOBS=4 mcpp buildon the same tree launchesninja … -j4, andMCPP_JOBS=2launches-j2(--jobs/-jis the same channel,src/cli.cppm:188-191setsMCPP_JOBS), so the observation method, reading the ninja argv withpgrep -afwhile a rebuild runs, does see the flag whenever there is one.Where it goes, read from the source.
src/config.cppm:517parses the key intocfg.defaultJobs, declared at:96under the comment "From config.toml [build]", and those two lines are the only mentions of the field in the repository: it has no consumer. (The key string itself also appears in four e2e fixtures and one CI action, each of them another copy of the generated file.)resolve_jobsinsrc/build/schedule/policy.cppm:242-265readsMCPP_JOBSand the manifest's[build] jobsand returns 0 when neither is set; the precedence comment at:127-129lists exactly those two before "0, meaning say nothing and leave the backend's own default". That 0 travels throughschedule::decideintoBuildOptions::parallelJobs(src/build/execute.cppm:774), andsrc/build/ninja_backend.cppm:2846-2847omits-jwhen it is 0. The global config never enters the chain.default_backendin the same table has the same shape: parsed at:518intodefaultBackend, never read.Why it matters more than a dead key usually does. The precedence comment itself makes the case (
policy.cppm:131-136): a single module compile peaks at 0.5–1.0 GB, and on a high-core, modest-RAM machine the backend default swaps. That is a property of the machine, not of a project, and the two knobs that do work are both the wrong shape for it.MCPP_JOBS(or--jobs) has to be said on every invocation, or baked into every wrapper and every recipe that calls mcpp.[build] jobsis per-package, and[workspace.build]rejects it ("has no key 'jobs' (or it is not inheritable)", the known-key list atmodules/manifest/src/toml.cppm:2652-2657, which is a reasonable rule), so a seven-member workspace would carry the number seven times and commit a machine property into the repository. The per-machine config is the natural home for it, mcpp already plants the key there, and it is the one place that does nothing. The machine this was found on has 8 cores and 15 GiB; ten concurrent module compiles at the sizes the comment quotes is the arithmetic that makes it unusable while a cold build runs.What would answer it. Either give
resolve_jobsthe global value as a parameter and place it between the manifest and the backend default —MCPP_JOBS>[build] jobs> globaldefault_jobs> 0 — so the existing comment stays true with one more step and "0 = say nothing" is unchanged for everyone who has not touched the key. A parameter rather than a config import, becauseresolve_jobsdeliberately carries no dependency beyond the manifest today (:138-139), and because it has a second caller atsrc/build/execute.cppm:2165for test-runner concurrency, where 0 falls back tohardware_concurrency()rather than to the backend; wiring the key in would cap that as well, which is probably what someone setting a machine-wide number wants, but it is a second behaviour to name. Or dropdefault_jobs(anddefault_backend, if it is likewise unread) from the template and the parser so the generated file stops promising them.