Skip to content

Commit 1b030db

Browse files
schmonzclaude
andcommitted
docs(backlog): three from today — build progress, artifact names, small programs
★★★ The quaude build is slow and opaque. The spinner's own comment says "no known total"; that stopped being true. graph.json names the work before the slow part starts (1795 modules, 173 assets, 33 cyclic requires), and quaude-fuse.js already prints those counts in the past tense. Also records that "has gotten very slow" is currently unfalsifiable — nothing keeps a per-step time a piped/CI build can read back — so the ask is steps, then a denominator per step, then a durable timing record. Measured: reading and parsing the 46MB graph.json is 187ms, so the minutes are elsewhere. Default artifact names should carry the Claude bundle version. quaude's default carries no version at all, naude's carries clode's; neither answers which Claude Code is inside. The obstacle is sequencing — resolveBuildOut runs at clode-fuse.cjs:1221, staged.key only exists at :1437. POSTSCRIPT, NOT DECIDED: clode as an orchestrator of small Unix programs. Half the shape exists already (the fuse worker and the naude assembler are spawned programs, quaude-fuse.js documents its own argv contract). Records the user's ambivalence as given, including the Windows worry, and the one measurement that cuts the other way: the step that is already a program has 0 win32 branches against 74 across the rest of the build path. Names the deciding measurement rather than assuming it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TpAHLcqXWW7LpzYPBgPXmQ
1 parent ee86c9d commit 1b030db

1 file changed

Lines changed: 222 additions & 0 deletions

File tree

‎BACKLOG.md‎

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,6 +3125,106 @@ attempts could from here: which volume has room, how much, and where `spawnSync`
31253125
puts its bytes on win32. Until then this guard is dark on Windows — and a skipped oracle reads
31263126
like 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
44924592
fixes 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

Comments
 (0)