@@ -3125,6 +3125,106 @@ attempts could from here: which volume has room, how much, and where `spawnSync`
31253125puts its bytes on win32. Until then this guard is dark on Windows — and a skipped oracle reads
31263126like a clean one, which is the whole reason it says so out loud.
31273127
3128+ ### ★★★ The quaude build is slow and opaque — name the steps, show how done we are (user, 2026-08-31)
3129+
3130+ ** The user:** "Building quaude has gotten very slow. We need explicit steps and a way to know
3131+ how done we are so that we can show the user progress."
3132+
3133+ ** What a build shows today.** ` clode build ` drives a TTY-only phase SPINNER
3134+ (` libexec/clode-fuse.cjs:708 ` , ` makePhaseSpinner ` ) with exactly three labels, set at
3135+ ` clode-fuse.cjs:1366 ` , ` :1569 ` and ` :1619 ` /` :1645 ` :
3136+
3137+ ⠹ Extracting bundle… (14.2s)
3138+ ⠹ Fusing… (211.7s)
3139+ ⠹ Smoking… (8.9s)
3140+
3141+ Its own comment says why it is a label and not a bar: "discrete phases with no known total".
3142+ That was true when it was written. It is not true any more — see below. Everything under
3143+ ` Fusing ` is one word for a minutes-long stretch that internally compiles ~ 1800 modules, merges
3144+ cyclic groups, packs assets, and links a template. The spinner is a liveness indicator, not a
3145+ progress indicator: it tells you the process has not died, and nothing else.
3146+
3147+ ** The denominator already exists on disk, before the slow part starts.** Measured here on the
3148+ staged 2.1.251 graph:
3149+
3150+ $ node -e "...JSON.parse(graph.json)..."
3151+ read 43ms parse 144ms moduleCount 1795
3152+ order array 1835 sources obj 1835 assets obj 173 cyclicRequires array 33
3153+
3154+ So ` graph.json ` names the work: 1795 modules to compile, 173 assets to pack, 33 cyclic requires
3155+ to merge. ` quaude-fuse.js ` already prints these AFTER the fact — "compiled N modules -> graph.qbc"
3156+ (` libexec/quaude-fuse.js:467 ` ), "pre-compiled N modules" (` :147 ` ), "N text assets ->" (` :462 ` ).
3157+ The numbers are known up front and reported only in the past tense. A counter is not a research
3158+ project; it is moving an existing number earlier.
3159+
3160+ ** And "very slow" is, right now, an unfalsifiable claim — which is the deeper half of the ask.**
3161+ No build records per-step time anywhere durable. The spinner's seconds are drawn to a TTY and
3162+ erased by ` done() ` ; a piped or CI build renders nothing at all. So "has gotten very slow" cannot
3163+ be checked against "was fast on <commit >", and we cannot say WHICH step grew. One cheap
3164+ measurement already narrows it: reading and parsing the 46MB ` graph.json ` costs 187ms total, so
3165+ the minutes are not I/O or JSON — they are in bytecode compilation, the merge, and the engine
3166+ build. That is a hypothesis, and it stays a hypothesis until each step is timed.
3167+
3168+ ** What this item asks for, then, is three things and they are ordered:**
3169+
3170+ 1 . ** Explicit steps.** A named, enumerated step list the build declares before it runs — not
3171+ three ad-hoc ` spin.phase() ` calls scattered across a 1678-line file. Whatever form the
3172+ out-of-tree/CMake work takes, the step list should come from the same place the build graph
3173+ does, so a step cannot be added without appearing in the display.
3174+ 2 . ** A denominator per step.** Compile is ` n/1795 ` . Assets are ` n/173 ` . Merge is ` n/33 ` groups.
3175+ Steps with no natural count (fetch a template, sign, smoke) stay labels, and that is fine —
3176+ an honest mixed display beats a fake percentage.
3177+ 3 . ** A durable timing record.** Every step's elapsed time written where a piped/CI build keeps
3178+ it, so a regression is a diff and not a feeling. This is what turns the user's report into
3179+ something we can act on, and it is also what makes any future "we made it faster" claim
3180+ checkable.
3181+
3182+ ** Why it belongs to ★★★ and not to a display tweak.** Steps you can show are steps you have
3183+ named, and steps you have named are a build graph. We do not have one — that is exactly what the
3184+ naude entry above ("The case for explicit dependencies") found the hard way, when a producer
3185+ stopped emitting ` cli.cjs ` and only a two-minute runtime path check noticed. A progress bar
3186+ bolted onto today's imperative script would be a fourth hand-maintained list of what the build
3187+ does, going stale the same silent way. Sequence this WITH the out-of-tree/CMake item, not before
3188+ it: get the steps declared, and the progress display falls out of the declaration.
3189+
3190+ ### Default artifact names should carry the Claude bundle version (user, 2026-08-31)
3191+
3192+ ** The user:** "Maybe the default built quaude/naude should have the Claude bundle version in
3193+ their filename by default."
3194+
3195+ ** Where the two defaults land today** (no ` --out ` ):
3196+
3197+ quaude ./quaude[.exe] resolveBuildOut, clode-fuse.cjs:772
3198+ naude build/clode-<CLODE ver>-<osToken>-<arch>/naude seaBin -> artifactDir, platform-tag.cjs:231
3199+
3200+ So quaude's default carries NO version of anything, and naude's carries ** clode's** version, not
3201+ the bundle's. Neither answers the question you actually ask of a built binary sitting in a
3202+ directory: which Claude Code is inside it? Build twice across an upstream bump and the second
3203+ ` ./quaude ` silently overwrites the first; keep both by hand and you are the one remembering which
3204+ is which. The binary itself knows (` --clode-attest ` reports ` bundleVersion ` ), but the filename —
3205+ the only part visible in ` ls ` , in a bug report, in a scrollback — does not.
3206+
3207+ ** The one real obstacle, and it is a sequencing one.** The output path is resolved at
3208+ ` clode-fuse.cjs:1221 ` (` resolveBuildOut ` ), and the bundle version only becomes known ~ 200 lines
3209+ later at ` :1437 ` , as ` staged.key ` from ` stageUpstreamCli ` — which is also where the manifest gets
3210+ its ` bundleVersion ` (` :1510 ` ) and ` --self ` gets none at all. So this is not a string edit in the
3211+ name formatter: either the default name is decided AFTER staging, or the bundle version is
3212+ resolved before it (` resolve.cacheKey ` , ` :457 ` , is what computes the key). Whichever way, the
3213+ ` --self ` branch has no upstream bundle and must keep a version-free default.
3214+
3215+ ** Open questions this needs answered before it is built, not while:**
3216+ - Does an explicit ` --out ` still win verbatim? (It must — CI passes the canonical published asset
3217+ name, and ` resolveBuildOut ` 's contract at ` :759 ` is load-bearing for attest/publish agreement.)
3218+ - Does naude's name become ` clode-<ver>-<token>-<arch>/naude-<bundle> ` , or does the bundle version
3219+ join the DIRECTORY key? The directory is the "if it's in build/clode-* , it's shippable" contract
3220+ (` platform-tag.cjs ` file header) — changing its shape is a bigger blast radius than changing a
3221+ basename.
3222+ - Should quaude's default also move under ` build/ ` rather than cwd? Adjacent to the ★★★
3223+ out-of-tree item; answer them together rather than twice.
3224+
3225+ Related: [[ Arch / artifact-name rationalization — release-atomic remainder (2026-07-27)]] above,
3226+ which is the same "one name, agreed by every consumer" problem on the publish side.
3227+
31283228### ★★★ Nothing gates the gates — make "a guard that cannot fail" structurally impossible (user, 2026-08-29)
31293229
31303230** The user:** "How are you finding these gaps? Is it automated into the build? Or is that another
@@ -4491,6 +4591,128 @@ number (`floor 6/6`) excludes G2, the only row that involves a real logged-in us
44914591** Do this before further feature work.** The 2026-08-24 release ships first because it
44924592fixes a confirmed 40-target cross-build DOA; the overhaul starts after.
44934593
4594+ ### POSTSCRIPT (user, 2026-08-31) — what if clode were an orchestrator of small Unix programs?
4595+
4596+ ** The user:** "What if clode were not one big program, but an orchestrator of several small Unix
4597+ programs, each of which does one job that composes into 'building quaude' (or naude)?"
4598+
4599+ ** WHERE THIS STANDS: NOT DECIDED, and recorded as a question, not a plan (user, 2026-08-31):**
4600+ * "Not 100% sure I want this design. It'll probably be really annoying on Windows, like everything
4601+ always is. But also it makes clear for me (and agents) what the steps in a build are, and whether
4602+ JavaScript is advantageous for these steps."*
4603+
4604+ So there are two payoffs and one tax, and they should be weighed as such rather than argued into
4605+ a foregone conclusion:
4606+
4607+ - ** Payoff 1 — legibility, for humans AND for agents.** This is the stated ★★★ goal ("tired of
4608+ the build being so baroque as to become opaque") applied to the one place it has not been: an
4609+ agent reading this repo today cannot enumerate the build's steps, because they are control flow
4610+ inside a 1678-line function, not things with names. Neither can a person.
4611+ - ** Payoff 2 — it forces the per-step language question to be asked per step.** Right now "what
4612+ language is this written in" is answered once, for a monolith. Programs with argv contracts let
4613+ the answer differ by step, and make a rewrite of ONE step a contained experiment. This is
4614+ exactly the open question in [[ What language is the SECOND half?]] above — that entry could not
4615+ reach a verdict partly because "the second half" is not divided into parts you could decide
4616+ about separately. Decomposition is the precondition for answering it.
4617+ - ** Tax — Windows, and the worry is well-founded** (see the Windows section below).
4618+
4619+ ** Half of this already exists, which is the strongest argument for the other half.** Two build
4620+ steps are ALREADY separate programs, spawned through a declared, injectable seam
4621+ (` clode-fuse.cjs:889 ` , "the one spawn seam every build step goes through"; the naude call takes
4622+ its own ` opts.spawnRun ` override at ` :1135 ` so it is stubbable without stubbing every other
4623+ spawn — a seam per program, which is what a program boundary looks like when it is real):
4624+
4625+ fuse spawnRun(template, ['run', libexec/quaude-fuse.js, <7 positional args>]) clode-fuse.cjs:1571
4626+ naude spawnRun(blobgenNode, [build-naude.mjs, --cli … --bundle … --out …]) clode-fuse.cjs:1136-1148
4627+
4628+ ` quaude-fuse.js ` even documents its own argv contract as a usage block (` :7-21 ` ) — signed base,
4629+ stage dir, node-shim dir, node_modules dir, bootstrap, extras.json, out. That is a Unix program
4630+ with a man page, already. So the proposal is not "restructure clode into a shape it has never
4631+ had"; it is "finish a shape it half has, and stop growing the parts that resisted."
4632+
4633+ ** What did not get decomposed, by line count:**
4634+
4635+ scripts/build-tjs.mjs 3796
4636+ libexec/clode-fuse.cjs 1678 resolve+stage+extract+close deps+sign+thin+fuse+smoke+attest
4637+ libexec/extract-claude-js.cjs 1593
4638+ libexec/scc-merge.cjs 1428
4639+
4640+ ** Why this is the same answer as the two items above, not a third project.** A step that is a
4641+ program has a name, a start, an end, and declared inputs and outputs. That is * precisely* what
4642+ the progress item needs (a step list you cannot add to without it appearing) and * precisely* what
4643+ the out-of-tree/CMake item needs (a dependency graph where a producer that stops producing is an
4644+ error at generate time, not two minutes into a build — the naude/` cli.cjs ` lesson above). Three
4645+ asks, one mechanism.
4646+
4647+ ** The obvious first extraction is also the slow one.** The cyclic-group merge is already a pure
4648+ function of the staged graph, already keyed, already cached to ` graph-merged.json ` — and it costs
4649+ ** ~ 380s of a 6:52 build** on a fast arm64 Mac (measured in situ; see the 1800000ms timeout
4650+ comment at ` clode-fuse.cjs:1585 ` ). A pure keyed transform from one file to another IS a Unix
4651+ program; today it is a stretch of ` Fusing… ` inside a worker nobody can run by hand.
4652+
4653+ ** Three questions this has to answer BEFORE it is built, because each one can invert the design:**
4654+
4655+ 1 . ** How does a fleet of programs ship as one binary?** clode's entire point is a single artifact
4656+ you can carry to a machine with no Node — see [[ clode is a BINARY WE BUILD, and only that]] .
4657+ Twelve programs must not mean twelve downloads. The busybox answer (ONE image, dispatch on
4658+ subcommand/argv[ 0] ) keeps single-artifact shipping AND gets separable programs, and quaude's
4659+ member/index layout is already a container for exactly that. Likely right — but decide it, do
4660+ not drift into it.
4661+ 2 . ** What is the interchange, given the payloads are huge?** Unix composition suggests pipes;
4662+ ours are a 46MB ` graph.json ` , a 49MB ` cli.cjs ` , and a template binary. Those are FILES in a
4663+ work dir, not a shell pipeline — the make/CMake shape, not the ` | ` shape. Say so explicitly,
4664+ or someone builds a pipeline that spends its life serializing. (Measured, for scale: reading +
4665+ parsing that ` graph.json ` is 187ms — cheap enough that a file boundary between steps costs
4666+ nothing next to a 380s merge.)
4667+ 3 . ** Which engine runs each program?** Not a stylistic choice: anything that emits quickjs
4668+ bytecode MUST run under the same tjs binary that becomes the template — the
4669+ runtime-compiles-for-itself rule (` quaude-fuse.js:1-5 ` ); bytecode from any other build is
4670+ undefined behavior. So the programs are inherently NOT uniform — some node-side, some
4671+ tjs-side — and the boundary falls exactly where the worker boundary already falls. That the
4672+ existing split lands on a real constraint rather than on tidiness is evidence the
4673+ decomposition wants to follow constraints too.
4674+
4675+ ** The failure mode to design against.** Splitting into programs without DECLARING inputs and
4676+ outputs just distributes the naude bug: a producer stopped emitting ` cli.cjs ` and only a runtime
4677+ path check, two minutes in, noticed. More programs = more places for that. The value is in the
4678+ declaration; the program boundary is what makes the declaration checkable. Do not take the
4679+ boundary and skip the declaration.
4680+
4681+ ** THE WINDOWS TAX — what is evidence, what is assumption.** Measured here, today:
4682+
4683+ win32/.exe/windows mentions, build path: build-tjs.mjs 26, clode-fuse.cjs 22,
4684+ build-naude.mjs 15, platform-tag.cjs 11
4685+ files carrying a #! shebang: 26 (inert on Windows — every call site must
4686+ name its interpreter, or the multi-call
4687+ binary makes the question moot)
4688+
4689+ Two live Windows entries above say the worry is not superstition: the deadlock guard cannot get
4690+ scratch space on Windows runners (deferred 2026-08-31, still dark), and the Windows-path ratchet
4691+ is blind on ~ 1335 lines of real code. Anything that multiplies process boundaries multiplies the
4692+ surface those two already fail on.
4693+
4694+ ** But one measurement cuts the other way, and it is the most interesting number here:**
4695+
4696+ libexec/quaude-fuse.js — the step that IS already a separate program: 0 win32 branches
4697+
4698+ The step with a real argv contract is the step with no Windows special-casing at all, because its
4699+ interface is "arguments in, file out" and it delegates path handling to the engine it runs under.
4700+ That is one data point, not a proof — it also happens to be the step that runs under tjs on every
4701+ platform. But it is the opposite of what "programs are annoying on Windows" predicts, and it is
4702+ worth knowing which way the effect actually runs before deciding.
4703+
4704+ ** So the deciding measurement is nameable, and we should take it before committing** (this is a
4705+ claim about how Windows behaves, so per doctrine it gets measured, not assumed): on a Windows
4706+ runner, time N sequential spawns of a multi-call binary against the same N steps called
4707+ in-process. If per-spawn cost is trivial next to a 380s merge, the tax is imaginary and the
4708+ legibility is free. If it is not, the answer is probably "programs as the DECLARED unit, some of
4709+ them dispatched in-process" — which keeps both payoffs and pays the tax only where it is cheap.
4710+
4711+ ** Cheap way in, no rewrite:** the spawn seam exists and is already injectable for tests. Lift one
4712+ step behind it (the merge), give it an argv contract like ` quaude-fuse.js ` 's, and see whether the
4713+ step list, the timing record, and the CMake edge all fall out of that one change. If they do, the
4714+ rest is the same move applied N times. If they don't, we learned it for the price of one refactor.
4715+
44944716## ★★ SHIPPED ENGINE TEMPLATES PREDATE THE uid/gid FIX — cross-fused Linux quaude is DOA (2026-08-09)
44954717
44964718** Every ` clode build --target linux-* ` produces a binary that cannot run on Linux
0 commit comments