Skip to content

fix: five silently-unwired capabilities — CI pointed at a deleted dir, deps that never re-installed, an engine nobody asked its version, a memory bridge that couldn't find airc, and a plugin frozen 2 weeks behind - #2213

Closed
joelteply wants to merge 9 commits into
canaryfrom
fix/ci-clients-gate

Conversation

@joelteply

@joelteply joelteply commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Eight commits, one theme: capability that exists, is correct, and never reaches the thing that executes. Each was invisible because the repo looked right — because it was.

1. The clients suite existed and nothing ran it

The workflow was working-directory: src — the deleted Node monolith — calling build:ts/test:crud. Rewritten to npm ci + typecheck:clients + test:clients on main AND canary.

2. The workspace repairs itself when the manifest moves

install.sh ran npm install once, at install time. Nothing re-ran it when the manifest moved, so a contributor who installed in June and pulled in August had a node_modules that silently didn't match its own package.json — and the first symptom pointed at missing source. Hooked to pre* on the client scripts only; start-server.sh stays headless-Rust by doctrine.

3. One front door: uu is the command

The binary derives its own name from argv[0], so uu --help says uu and any future alias is correct the moment it exists. Reports build number + sha + built-at.

4. The daemon asks the engine what it IS

A wedge got misattributed to "the fork bump in build 4577" — a Rust core build number read as the engine's. Settling it meant comparing binary mtimes on two machines. But llama_build_info() existed all along, and the daemon already GETs /props for the served window — and threw build_info away. Now engine_build() rides ServingSnapshot and surfaces as engineBuild on ai/inference/status.

5. ninja becomes a manifest module; cmake/ninja stop being CUDA's business

Both pins lived inside a nvcc && !cl.exe guard, so a CPU-only Windows box got no cmake pin at all and died with "is cmake not installed?" while cmake sat a directory away. PATH knowledge moved to [module.runtime_path] where the windows-vs-unix split belongs as data. And ninja was a hardcoded URL with no sha256 inside the llama-server module — invisible to the drift gate. Now a real Mod-Ninja, sha-verified, platforms = ["windows"] because the defect it fixes is Windows-only.

6–8. The memory bridge, and why its fixes wouldn't have shipped

Engram recall was silently off: hooks don't inherit the operator's shell, airc lives in ~/.local/bin, so bare airc was never found — and the receipt reported that as "airc status down" about a daemon at 21h uptime with 1305/1305 acked. Fixed via the same resolve_continuum shape the file already had, plus receipts that name the measured cause.

Then the bigger one: the running plugin was a marketplace copy pinned to a sha from July 25, missing session-capture.sh entirely — automatic per-turn capture had never run once. claude plugin update is version-based, and the version had never been bumped, so no content change ever propagated. Bumping 0.1.0 → 0.2.0 shipped two weeks of fixes in one command (verified). Added check-plugin-version.sh + CI gate so it can't silently freeze again, and a staleness detector so a frozen copy announces itself.

Verified: cargo check --lib --tests clean, snapshot_mapping_is_honest passes, manifest-gen --check OK (4 files in sync), ninja absent from the linux/macos projections, PowerShell parse + bash -n clean.

Deliberately not done: the gate is NOT wired into .githooks/pre-commit — that hook runs two test files deleted with the Node monolith and core.hooksPath doesn't point at it, so no pre-commit hook runs at all. Wiring a gate there would look enforced while running never. Flagged, not silently patched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc

…hat should have was pointed at a deleted directory

Three defects that compose into one: the TypeScript client tests were unguarded,
the guard that should have covered them was dead, and on Windows the suite could
not be run at all.

1. THE SUITE WAS RUN BY NO WORKFLOW. `npm run test:clients` was already in
   package.json and already passing. Nothing invoked it. So renderBench.spec.ts
   and six sibling spec files across apps/web and packages/ existed, were green
   on their authors' machines, and gated nothing — a correct check nothing calls,
   indistinguishable from having no check until someone greps for the caller.

2. ci.yml HAD BEEN RED FOR TWO MONTHS. Last success 2026-06-07; failing
   continuously from 2026-06-09 with `Cannot find module 'dotenv'`. Root cause
   was not dotenv: the job ran in `working-directory: src` against
   `src/package-lock.json`, calling `npm run build:ts` and `npm run test:crud`.
   `src/` was the Node monolith, retired when the substrate became a headless
   Rust core (#1840). Every path and script it named is gone; the error was
   describing the absence of its whole world.

   A check that ALWAYS fails is worse than no check — it cannot distinguish a
   broken PR from a healthy one, so the only lesson available is to stop reading
   CI. That is what happened: work routed around it through the Rust and
   drift-guard workflows for two months. Replaced rather than patched, and it now
   watches `canary` as well as `main`, because a gate that only sees the stable
   line learns about breakage after the merge.

3. THE SUITE COULD NOT RUN ON WINDOWS AT ALL. `@continuum/web` and
   `@continuum/chat-view` declared `"test": "TZ=UTC vitest run"`. npm runs
   scripts through cmd.exe on Windows, which has no POSIX env prefix, so both
   died with `'TZ' is not recognized as an internal or external command`. The
   other three workspaces use a plain `vitest run` and were fine — the split is
   exactly the prefix.

   Fixed by REMOVING the portability assumption rather than packaging a tool to
   satisfy it: apps/web already pinned `test: { env: { TZ: 'UTC' } }` in its vite
   config, making the prefix redundant; chat-view had no config at all, so it
   gets one whose only job is that pin. No cross-env dependency added.

   Worth stating: this one is only findable by RUNNING the suite on Windows.
   Linux CI and macOS both pass. Adding the CI gate alone would have gone green
   and left every Windows contributor unable to run the tests locally — the gate
   would have hidden this rather than caught it.

Verified on Windows after the fix: 25 test files, 153 tests, exit 0. Before it,
the two affected workspaces produced no test run whatsoever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
joelteply and others added 2 commits August 8, 2026 22:30
… client scripts only

`install.sh` runs `npm install` ONCE. Nothing re-ran it when the manifest
changed. So a contributor who installed in June and pulled in August had a
node_modules that silently did not match its own package.json, and the first
symptom was every client spec failing at COLLECTION with:

    Failed to load url @continuum/chat-view. Does the file exist?

which points a newcomer at missing SOURCE rather than at their missing deps.
Measured on a real checkout: tree from Jun 17, manifest from Aug 5, `lit` and
every @continuum/* workspace package absent, seven spec files dead.

WHERE IT HANGS, AND WHY NOT THE START PATH. `start-server.sh` is headless Rust
by doctrine — "No Node, no TS, no widgets. The Node orchestrator stays out of
the loop." A dependency guard wired there would drag npm into the one runtime
path that exists to avoid it. So this hangs off `pre*` hooks on the CLIENT
scripts only: dev:web, dev:desktop, build/lint/typecheck/test:clients. Anyone
who only ever runs the core pays nothing and never sees it execute.

mtime, not a checksum: npm writes node_modules/.package-lock.json when it
materialises the tree, so "tree older than manifest" is exactly the question
worth asking and needs no parsing. A checksum would be more precise about
CONTENT and no more precise about what actually breaks people.

LOUD, never silent. A guard that repairs things without saying so teaches the
operator that installs are magic and hides a real signal — a lockfile moving
under them — that is sometimes worth knowing. Skipped under CI, where `npm ci`
is authoritative and already ran; CONTINUUM_SKIP_DEP_CHECK opts out for anyone
hand-managing a tree.

Verified both directions: silent on a fresh tree, fires with the reason named on
a stale one, no-ops under CI=1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
…its own name

Four names were in play and two were installed:

  uu          ON PATH   core/continuum-core/src/bin/continuum.rs
  continuum   ON PATH   same build, same size, same mtime — one binary, two names
  ctm         absent    declared as [[bin]] in apps/cli/Cargo.toml, never installed
  jtag        absent    used 40 times in CLAUDE.md, installed by nothing (Node era)

So the declared name, the documented name, and the installed names were three
different answers and nothing reconciled them. Joel reached for `uu` and was
right; I nearly "corrected" him from the stale Cargo.toml, which would have been
ratifying a declaration over the live system.

WHAT CHANGED

`program_name()` derives from argv[0]. The usage text was the literal string
"usage: continuum ...", so `uu --help` printed examples for a command the reader
had not typed — the front door did not know its own name. One binary can now
ship under any number of names and each tells the truth; a future alias is
correct the moment it exists, with nothing to remember to update.

`version` is handled LOCALLY and never dispatched. `uu version` used to fall
through to the substrate and return `Unknown command: 'version'` — the CLI could
ask the core what IT was and could not say what ITSELF was, and with no core
running it answered nothing at all. That is the gap that lets someone debug a
fixed bug with an unfixed binary in their hand, which is the whole point of the
version ruling.

CLAUDE.md: 38 `./jtag` invocations rewritten to `uu`. Deliberately NOT rewritten:
`.continuum/jtag/logs/...` paths (3, real directories), the
[[jtag-probes-are-rtos-debugger]] memory slug, and the legacy JTAGClient
snippets — none of those are commands, and a blind replace would have broken
log paths.

The mistake entry itself was inverted and is now the correction: it told every
new session "Always work from `src`" and "Commands: `./jtag` NOT `./continuum`".
`src/` is the retired Node monolith, `jtag` is installed by nothing, and
`continuum` — the one it warned against — works. That is a documentation lie
with a live cost: it is the first thing a fresh agent reads and the first ten
minutes it wastes.

Env vars corrected mid-edit: I wrote CONTINUUM_BUILD_SHA/_BRANCH from memory;
build.rs emits CONTINUUM_BUILD_GIT_SHA and no branch at all. `option_env!` would
have silently printed "unknown" rather than failing, so that would have shipped
as a quiet lie about the build.

cargo check -p continuum-core --bin continuum: 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
@github-actions github-actions Bot added size: L and removed size: M labels Aug 9, 2026
joelteply and others added 2 commits August 9, 2026 12:36
…nfo on the snapshot

A per-slot wedge was attributed to "the fork bump in build 4577". 4577 is the
Rust CORE's build number; the engine has its own, unrelated one, and the fork
engine had not been rebuilt at all. Settling it took comparing binary mtimes on
two machines.

I told M5 the engine had no version surface. That was wrong: llama_build_info()
has existed all along — `--version`, the startup log, `/props.build_info`, and
`system_fingerprint` on every completion. The gap was entirely on OUR side. The
daemon already GETs `/props` for the served window and the modalities verdict,
and threw `build_info` away.

So this is silently-unwired-capability with the polarity reversed: not something
we built and failed to wire, but something upstream hands us free that we drop
on the floor. Same cost — a fact available for the asking gets re-derived by
archaeology, and the derivation was wrong.

- LlamaServerControl::engine_build() reads /props.build_info, default Ok(None)
  so fakes and remote controls stay honest by construction
- rides on ServingSnapshot, stamped at reconcile, probed on the reconcile line
- surfaces as engineBuild on ai/inference/status

The commit sha is the load-bearing half: build numbers are ancestor counts, so
our fork and upstream can both say b6789 and mean different code.

Three assertions pin it: the identity must reach the snapshot; an engine that
cannot say what it is reads as unknown rather than a guess; and a not-live
snapshot never names an engine even when one answered the probe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
… CUDA's business

Two defects, both found by a plain `cargo check` dying with "is `cmake` not
installed?" on a box where cmake was installed a directory away.

1. The cmake + ninja pins lived INSIDE windows-build-env.sh's CUDA block, gated
   on `nvcc present AND cl.exe absent`. Neither is a CUDA concern. A CPU-only
   Windows box (no nvcc) skipped the block and got no cmake pin at all; a shell
   where cl.exe already resolved skipped it for the same reason. Hoisted into
   two self-guarded blocks that run on their own applicability.

   The PATH half is now the manifest's job — [module.runtime_path] on cmake and
   ninja, consumed by the generic loop that was already there. That is where the
   windows-vs-unix split belongs, because it IS a packaging fact: brew/apt put
   these on PATH, the Windows archives do not. Linux/macOS projections carry
   nothing, as they should.

2. ninja was fetched by a HARDCODED url inside the llama-server PowerShell
   module — no manifest entry, no pinned sha256, invisible to the manifest-gen
   drift gate. One tool provisioned by different rules than every other tool is
   exactly the drift the manifest exists to prevent, and an unverified download
   is a supply-chain hole however convenient the url is. Now a real Mod-Ninja
   with the same guard shape, sha256 verification and source-of-truth as
   Mod-CMake, and it runs in install.ps1 beside Mod-CMake instead of only on
   boxes that had already built llama-server with CUDA.

ninja is declared platforms = ["windows"] deliberately. The defect it fixes is
Windows-only (cmake auto-picks the newest VS; "Visual Studio 18 2026" is a
generator cmake 3.30.x cannot name). On unix the default generator is never
broken, so listing macos/linux would make those contributors install a package
to buy nothing. Adapting to the platform means stating the asymmetry, not
smearing one platform's workaround across all three.

Verified: manifest-gen --check OK (4 files in sync), ninja absent from the linux
and macos projections, CMAKE and CMAKE_GENERATOR=Ninja both resolve from a fresh
shell with no CUDA involved, Mod-Ninja/Mod-CMake idempotent-skip, PowerShell
parse + bash -n clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
@github-actions github-actions Bot added size: XL and removed size: L labels Aug 9, 2026
joelteply and others added 3 commits August 9, 2026 13:18
…own" for "program not found"

Engram recall has been silently off on BigMama. Every session start printed
"MEMORY BRIDGE DOWN — could not resolve this agent's persona id (airc status
gave nothing)" while airc had 21h uptime and 1305/1305 acked.

The probe never observed the daemon. Hooks do not inherit the operator's
interactive shell — airc installs to ~/.local/bin, routinely absent from a
hook's PATH — so bare `airc` was not a program it could find, and 2>/dev/null
swallowed the "command not found" that would have said so.

The same file already had resolve_continuum() doing this correctly. Two answers
to "find a binary" in one file, one of them robust, is the drift; now there is
one shape (resolve_airc, honoring $AIRC_BIN, with .exe variants for Windows).
share.sh's two bare `airc` calls go through it too.

And the receipts stop guessing. "No airc binary" and "airc ran and reported
nothing" are different types, not two values of one type — conflating them is
what wrote "airc status down" about a healthy daemon, twice, and sent an
afternoon of diagnosis at the wrong layer. persona_failure_reason() re-derives
the measured cause for both the receipt and the notice the AGENT reads.

It re-derives rather than setting a variable inside resolve_agent_persona
because callers invoke that as $(...) — a subshell, where any assignment dies.
The first version of this fix did exactly that and the receipt came out blank.
The negative test caught it, which is why it exists.

Verified both directions: with ~/.local/bin stripped from PATH (the environment
that failed for two sessions) the id now resolves; with no airc reachable at all
the failure names the real cause instead of blaming the daemon.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
…indistinguishable from "stale"

The bridge's founding rule is that "installed" must never be indistinguishable
from "working". This is that rule one level up, and it is why yesterday's fixes
would have changed nothing on the machine that needed them.

There are two install paths with very different freshness semantics:

  * `claude --plugin-dir tools/plugins/memory-bridge` runs LIVE from the repo —
    `git pull` IS the update.
  * a marketplace install COPIES the plugin to
    ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/ and pins it to a
    git sha. Nothing re-syncs it. `git pull` changes nothing.

Nothing told you which one you were on. Measured on BigMama: the running copy
was pinned to 60fa0db (2026-07-25), two weeks stale. Its lib.sh had no
persona-id cache, and it contained NO session-capture.sh at all — so automatic
per-turn capture, the entire "volitional memory isn't memory" point, had never
run once on that machine. The README said the bridge was live the whole time.

So session-recall now compares its own running location against
tools/plugins/memory-bridge/scripts in the current checkout, and on drift emits
⚠️ MEMORY BRIDGE STALE plus a `stale` receipt. A missing file counts as drift —
that is exactly how session-capture.sh went absent unnoticed.

It runs on the SUCCESS path too, because staleness is orthogonal to whether
recall worked: a frozen copy can recall perfectly and still be missing every fix
since it was installed.

Only a cached copy can be stale, so running from the repo stays silent; and if
the cwd is not the continuum checkout there is nothing to compare against, so it
says nothing rather than guessing.

README: the "Status: Live" line was a claim a README cannot make — it describes
the repo, while liveness depends on your install. Replaced with the two paths
and how to tell which you are on.

Verified against the real frozen copy on this box (4 of 4 scripts differ →
notice fires) and against a live repo run (silent).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
…l reason fixes never shipped

Plugin updates are VERSION-based, not content-based. `claude plugin update`
compares the DECLARED version against the installed one and never looks at the
files. memory-bridge had sat at 0.1.0 since 2026-07-25, so every installed copy
answered:

    ✔ memory-bridge is already at the latest version (0.1.0).

...no matter what changed. That single static string is why two weeks of work
never reached the machine that needed it: the copy running on BigMama had no
persona-id cache and no session-capture.sh AT ALL, so automatic per-turn memory
capture — the whole "volitional memory isn't memory" point — had never run once,
while the repo held working code and the README said the bridge was live.

Bumping 0.1.0 -> 0.2.0 propagated all of it in one command, verified here:
marketplace update -> `Plugin "memory-bridge" updated from 0.1.0 to 0.2.0`, the
new copy carries session-capture.sh + resolve_airc + the staleness detector, and
now differs from this tree in 0 of 4 scripts.

So the guard. check-plugin-version.sh fails when files under a plugin dir change
without that plugin's plugin.json version changing, and it compares the version
BEFORE and AFTER rather than trusting that the manifest was touched — editing a
description is not a release. A brand-new plugin has nothing to bump from and
passes. Verified both directions: passes on this commit's real bump, fails on a
simulated missed one.

Wired into CI, deliberately NOT into .githooks/pre-commit: that hook invokes
tests/adversarial-protocol.test.cjs and tests/command-processing.test.cjs, both
deleted with the Node monolith, and core.hooksPath does not point at it — so no
pre-commit hook runs at all right now. Adding a gate there would have looked
enforced while running never, which is the same defect one level up. Flagged
rather than silently repaired; that hook needs its own decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
@joelteply joelteply changed the title fix(ci): the clients suite existed and nothing ran it; the workflow that should have was pointed at a deleted directory fix: five silently-unwired capabilities — CI pointed at a deleted dir, deps that never re-installed, an engine nobody asked its version, a memory bridge that couldn't find airc, and a plugin frozen 2 weeks behind Aug 10, 2026
@joelteply

Copy link
Copy Markdown
Contributor Author

Rebase attempt 2026-08-14: first two commits rebased clean (CLAUDE.md resolved to current continuum doctrine — the branch's edits there were Node-era uu/npm-start guidance; bin/continuum.rs resolved keeping canary's newer start-flag handling PLUS this PR's local version arm). Then commit 779810a (ninja→manifest module) conflicts across 4 files in the windows-install seam that #2249 just merged into — including BOTH generated manifests (manifest.windows.{ps1,sh}), which must be REGENERATED from a resolved install-manifest.toml, not hand-merged. Windows install behavior also can't be validated from this Mac. Needs a deliberate rework on top of merged #2249 — likely the Windows-lane owner's pass. The partial resolution above is preserved locally on branch pr2213 if useful.

# Conflicts:
#	CLAUDE.md
#	core/continuum-core/src/bin/continuum.rs
#	core/continuum-core/src/inference/llama_server.rs
#	core/continuum-core/src/modules/serving_daemon.rs
#	tools/scripts/generated/manifest.windows.ps1
#	tools/scripts/generated/manifest.windows.sh
#	tools/scripts/install-manifest.toml
#	tools/scripts/lib/windows-build-env.sh

@joelteply joelteply left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IntelMac non-author review] Reviewed because this PR's theme — capability that exists, is correct, and never reaches the thing that executes — is the same failure I have hit seven times on the grid tonight, so I read it looking for that shape in the fixes themselves rather than only in what they fix. Two findings, one of them the PR's own lesson stopping one line short. Nothing blocking.

WHAT I CHECKED RATHER THAN ASSUMED, since a gate that cannot run is the exact defect being fixed here: typecheck:clients, test:clients, build:clients, lint:clients, dev:web, dev:desktop and deps:ensure all exist in package.json on fix/ci-clients-gate. The two the workflow invokes are real. And ensure-node-deps.sh hard-exits on CI before doing anything, so the pre* hooks cannot make npm ci's lockfile authority negotiable in the workflow — I went looking for that specifically and it is already closed, with the reasoning written down.

FINDING 1 — persona_failure_reason STILL SWALLOWS THE ONE THING THAT WOULD ANSWER IT. This is the finding I would act on.

The diagnosis on this hunk is that 2>/dev/null ate the command not found that would have named the cause, so a resolver failure got written into two sessions' receipts as airc status down about a daemon at 21h uptime. Correct, and the resolver fix is right. But the status call keeps the redirect, in both places:

live="$("$airc_bin" status 2>/dev/null | awk '/^peer_id:/{print $2; exit}')"

if [ -z "$("$airc_bin" status 2>/dev/null | awk '/^peer_id:/{print $2; exit}')" ]; then
  printf 'airc found at %s but it reported no peer_id — daemon down or not joined' "$airc_bin"

So the receipt now asserts a DISJUNCTION — "daemon down or not joined" — and the stream that distinguishes those two is discarded at the only point where it exists. A daemon that is genuinely down, a scope that never joined, a socket whose permissions changed, and an airc that panics on startup all produce that identical sentence. You have gone from one confidently wrong cause to two confidently listed candidates, which is a real improvement and is still the same shape: the process that KNOWS is silenced, and the explanation is reconstructed by inference afterwards.

The fix is small and lives on the path where the file already argues cost does not matter ("only ever runs on the failure path, where a fraction of a millisecond buys a receipt that names the actual cause"): capture stderr instead of dropping it, and put its first line in the receipt.

err="$("$airc_bin" status 2>&1 >/dev/null | head -1)"
printf 'airc found at %s but reported no peer_id (%s)' "$airc_bin" "${err:-no stderr}"

I am flagging this rather than shrugging at it because your own commit message is the argument for it: "'No airc binary' and 'airc ran and reported nothing' are different types, not two values of one type." Agreed — and "the daemon is down" and "this scope never joined" are also different types, currently collapsed into one string. The type distinction you drew is right and stops one level too early.

FINDING 2 — THE CLIENTS GATE NOW RUNS ON EVERY RUST-ONLY PUSH, AND THE SKIP IT REPLACED IS GONE.

on:
  push:    branches: [ main, canary ]
  pull_request: branches: [ main, canary ]

with no paths: filter. The old workflow had a documentation-only skip; it was broken (it diffed origin/main..HEAD, which is the wrong base for a canary PR) and removing it is right. But nothing took its place, so on a repo whose overwhelming majority of changes are Rust under core/, every one of them now pays npm ci plus the full client suite to assert something no Rust commit can affect.

That is not a correctness bug and I would not hold the PR for it. I raise it because the failure mode it invites is precisely the one documented at the top of this file: a check people learn to ignore. A gate that is red for two months teaches everyone to stop reading CI; a gate that is slow and irrelevant on nine of ten PRs teaches the same lesson more slowly. A paths: filter on apps/**, packages/**, sdk/**, package.json, package-lock.json and the workflow itself would keep it meaningful — with the caveat worth stating explicitly, since it is the reason to think before adding one: a required check with paths: never reports on filtered PRs, so if this is ever marked required, use paths-ignore or a skip-job that reports success instead.

TWO THINGS I WENT AFTER AND FOUND CLEAN, recorded so nobody re-runs them: (a) stale_install_notice counts a file missing from the INSTALL as drift, via cmp against a nonexistent path — that is the session-capture.sh case it was written for, and it works; a file missing from SOURCE is skipped, which is the right asymmetry. (b) persona_failure_reason re-deriving instead of setting a variable is correct and the comment explains why — command substitution is a subshell and an assignment inside it dies with it. That one is worth keeping in the file forever; it is the kind of thing that gets "simplified" back into a bug in a year.

ONE OBSERVATION, NOT A REQUEST. The header comment on ensure-node-deps.sh says the guard hangs off "dev:web / build:clients / test:clients / typecheck:clients" — four. The manifest wires six, adding predev:desktop and prelint:clients. The code is what I would want; the comment undercounts it. Tonight I have hit three separate cases on this grid of a doc asserting something the code does not do, and the reflex to write "the comment is what is stale" is the one I keep having to correct in myself, so: the CODE is right here, the prose merely lags it by two entries.

@joelteply

Copy link
Copy Markdown
Contributor Author

Stale-PR audit (2026-09-09, by the author). Labelling this rather than leaving its state ambiguous — an open PR reads as work in flight, and this one is not.

opened            2026-08-09
commits not in canary   9
files still differing   25
mergeable               DIRTY (conflicts with canary)

Five silently-unwired capabilities. The findings here may outlive the patch — if the five are still unwired, that is worth a card even if this diff never lands.

No action taken. I am not closing it unilaterally — it is real work and the areas it touches now have other owners. What I am doing is removing the ambiguity: as of today this is not in flight, nobody is rebasing it, and it will not merge without a deliberate decision.

The decision someone should make, in the order I'd suggest:

  1. Is the problem it solves still real, or has canary solved it another way in the intervening ~1,700 PRs? Check that FIRST — I filed a duplicate of an already-merged fix tonight for skipping exactly this step.
  2. If still real and worth the conflict resolution, say so and I'll rebase it.
  3. If still real but not worth this diff, close it with a pointer to a fresh card so the problem survives even though the patch does not.
  4. If no longer real, close it.

Option 3 is the one I'd default to for anything with a large conflict surface: the branch persists after closing, so nothing is lost, and the board stops carrying a false signal.

cc the room — raised in #continuum, no objection recorded.

@joelteply

Copy link
Copy Markdown
Contributor Author

Closing: red for six weeks; the five capabilities it wired were re-wired piecemeal since (the flywheel #4-series, the vision sidecar, the recorder). Anything still missing gets its own card with today's tree, not a rebase of an August one.

@joelteply joelteply closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant