Skip to content

mcpx testing: a fast, sharded CI with a time budget, linters and auto-format #1901

Open

Description

@dezren39

Relocated from dezren39/darwin-nix#95 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).

Parent: #91

Purpose

CI for mcpx, built to the notes' stated top priority: "the main priority is being short as possible", with its stated focus, "main focus is strong tests as each step of the lifecycle". Those two pull against each other, and this tracker is where the trade is made explicitly rather than by accident.

Where it starts, measured rather than estimated. One workflow, .github/workflows/mcpx.yml, one ubuntu-latest job. Run 36673523537 (push to main), from the step timestamps in gh run view 36673523537 --log:

Step Seconds
Set up job, checkout, nix-installer, setup-go/deno/bun/node 32.6
go test ./... -count=1 134.8
nix build .#packages.x86_64-linux.mcpx 58.8
Post steps + complete 1.5
Total wall clock 229 s

Inside the test step, internal/e2e alone is 100.727 s of 134.8; internal/tui 8.6 s; internal/pool 1.5 s; internal/events 1.05 s; internal/mcpserver 1.03 s; everything else under 0.5 s. The last eight runs sit between 3 m 57 s and 4 m 18 s. So the whole sharding question is, in the first instance, one question: what do you do about e2e, which is 44% of wall clock and 199 of 719 tests.

After #182, at 05c78b2 (run 36693030957, push to main): 4 m 09 s wall; setup 22 s; go test 148 s with internal/e2e at 108.9 s for 209 of 735 tests; nix build 70 s (checkPhase 18.5 s). That run's Go cache was a miss by construction (the first main run with the new key), so the warm-cache number is still owed -- #143 records it.

Three facts about today's setup that this tracker exists to fix, all verified:

  • The Go cache has never restored. Fixed in Phase 5: tag event bodies must travel with the event #182. actions/setup-go@v7 defaults cache: true but looked for go.mod at the repository root; mcpx's is at pkgs/mcpx/go.mod. The log carried ##[warning]Restore cache failed: Dependencies file is not found and every run re-downloaded every module and compiled cold. Phase 5: tag event bodies must travel with the event #182 added cache-dependency-path: pkgs/mcpx/go.sum (.github/workflows/mcpx.yml:50); the key now saves. What remains of caching (restore-only on PRs, the Nix store, toolchains) is Update Rust crate tokio to v1.38.2 [SECURITY] #135.
  • treefmt.nix exists and nothing runs it. programs.nixfmt and programs.rustfmt are enabled, flake.nix exposes checks.formatting, and no workflow invokes either. treefmt.nix:6 also carries # programs.shellcheck.enable = true; # failures -- a linter switched off rather than satisfied, already in the tree, and the precedent any new linter must avoid repeating.
  • Unit tests run twice per CI run. package.nix checkPhase re-runs a list of unit packages inside nix build, costing 16.55 s (18.5 s at 05c78b2) on top of the go test step. That is either free time to reclaim or a deliberate sandbox check worth keeping; Update Rust crate tokio to v1.38.2 [SECURITY] #135 decision 6 now argues to keep it (the sandbox has no network, which would have caught Update Rust crate axum to 0.6.20 #65) but take it off the critical path, and to fix its drifted package list (12 packages with tests never run there).

What this tracker does not own: the seed itself (#93 -- the init job mints and exports it, but #121/#122 decide what it is), the environment legs that join this matrix (#97 -- nix, container, remote, other OSes), and what an agent runs on its own machine instead of CI (#99).

Children

Proposed order

  1. Update Rust crate tokio to v1.38.2 [SECURITY] #135 -- the skeleton. Workflow name, init -> matrix -> ci-ok job graph, triggers (including the notes' "commit/open/undraft to a non-draft pr", which today's workflow does not implement -- it sets no pull_request: types:, so it uses the default [opened, synchronize, reopened] and ready_for_review never fires), concurrency, caching, checkout, and what stays behind in mcpx.yml. Everything else plugs into this.
  2. 🎨 Palette: Search panel accessibility improvements #143 -- build the first real iteration and record a baseline. Split unit from e2e, put static shards in, add the lint leg, keep nix build. (The Go cache fix it also carried landed separately in Phase 5: tag event bodies must travel with the event #182, as Phase 3: sync, blob gate, and relays #181's phase 0 asked; chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to e7457f6 #52, which the old order waited on, is closed by the same PR.) This is the issue that produces the timing data every later decision needs; without it Update dependency hyperscript.org to v0.9.93 #139 and Update dependency vitest to v5 [SECURITY] #140 are guesswork. Update dependency htmx.org to v1.9.12 #138 (in Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /sources/chat #97) is recommended just before or alongside it: it is the remaining isolation gap, and every shard inherits it.
  3. Update dependency hyperscript.org to v0.9.93 #139 -- replace the static shards with a split driven by measured per-test time, and write down the rule for when to stop splitting. Needs 🎨 Palette: Search panel accessibility improvements #143's numbers. t.Parallel in e2e is no longer blocked by chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to e7457f6 #52; it waits on Update dependency htmx.org to v1.9.12 #138 and a measured -parallel.
  4. Update dependency vitest to v5 [SECURITY] #140 -- turn the recorded baseline into a gate: the budget job, and the runbook for what to do when it is breached. Needs a number, which 🎨 Palette: Search panel accessibility improvements #143 supplies.
  5. Bump dezren39/nix flake input to 3499ed3 — opencode now defaults to v2 #141 -- the full formatter and linter set, beyond the first lint leg 🎨 Palette: Search panel accessibility improvements #143 stands up.
  6. ⚡ Bolt: Optimize search match counting performance #142 -- format.yaml, last, because it only makes sense once the formatters are pinned (Bump dezren39/nix flake input to 3499ed3 — opencode now defaults to v2 #141) and there is a green test workflow for it to key on (Update Rust crate tokio to v1.38.2 [SECURITY] #135's name:).

The notes describe an iterative loop -- split the longest job, re-measure, repeat, and do it again whenever the budget is breached. That loop is #143 -> #139 -> #140 -> #143, and it is expected to run more than once. #140's runbook is what makes it a procedure rather than a habit.

Children -- ease and impact

# Title Kind Ease Impact
#135 workflow skeleton -- init, matrix, final job, triggers, caching (cache line done in #182) design M high
#139 shard by measured time, and when to stop splitting design M high
#140 time budget in the final job, and the over-budget runbook design + implement S (job) + S (runbook) med
#141 every formatter and linter in the matrix, pinned by the flake design M (mostly clean-up, one style decision) med
#142 format.yaml -- auto-format after a green run design M (needs a token decision) low-med (convenience; quality comes from the blocking check)
#143 first iteration + recorded baseline implement M high -- roughly halves wall clock and produces the data for everything else

Warnings carried by this tracker

  • The biggest single win is no longer blocked by chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to e7457f6 #52, but it is not free. 209 serial e2e tests that mostly wait on process start, sockets and runtime startup are the textbook case for t.Parallel() -- plausibly 2-3x on one runner at zero extra setup cost. Phase 5: tag event bodies must travel with the event #182 fixed the leak that made that dangerous (stop --all through the info files, 60 s idle exit). What still has to come first is Update dependency htmx.org to v1.9.12 #138: newEnv still inherits the caller's environment, so an exported MCPX_DAEMON_ENDPOINT/MCPX_SOCKET sends every parallel test to one outside daemon. And one e2e test is 3-4 processes (CLI, daemon, fake server, often a JS runtime), so -parallel 8 on 4 vCPU is 24-32 processes: measure -parallel, do not take GOMAXPROCS. Update dependency hyperscript.org to v0.9.93 #139 has both.
  • Renaming a job invalidates every required check, and a sharded matrix produces N check names that change on every rebalance. The fix -- one needs: [...] if: always() aggregator as the sole required check -- is in Update Rust crate tokio to v1.38.2 [SECURITY] #135, along with the trap that without always() a skipped aggregator reads as neutral and a red matrix merges.
  • A paths:-filtered required check never reports and blocks merges permanently. Harmless today only because nothing is required yet.
  • Sparse checkout breaks nix build, which evaluates the whole flake. Sparse applies to test legs only.
  • workflow_run only fires from the default branch, checks out the default-branch tip rather than head_sha, and types: [completed] includes failures. All three bite ⚡ Bolt: Optimize search match counting performance #142.
  • The GITHUB_TOKEN loop guard is the only thing that closes the auto-format loop. Swapping in a PAT to get past branch protection re-opens it.
  • More shards are not monotonically better. 22-33 s of setup is the floor per leg (32.8 s on 36673523537, 22 s on 36693030957); ten legs spend 220-330 runner-seconds of setup to save ~90 s of wall clock, and the concurrency cap turns them into two waves -- which is exactly the runner-pressure effect the notes predict. Driving the fixed cost down beats adding a fifth shard.

Related

#91 (parent), #93 (#121/#122 -- the seed init mints), #97 (#134/#136 -- environment legs join this matrix), #99 (#127 -- CI is where "every test" runs so that agents do not), #49 (CI for every flake package -- overlaps this tree directly), #138 (in #97; the isolation gap before parallel shards), #178 (also edits package.nix), #52 (closed, fixed in #182), #79, #35, #65, #47, #48.

Security/auth deferred; see tracker.

Acceptance criteria

  • mcpx CI runs sharded with a stated time budget; lint and auto-format checks run as their own jobs and fail the build on violations.
  • The budget and shard layout are documented, and a deliberately slow test is shown to trip the budget alarm in CI.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/darwin-nix#95 (verbatim, oldest first):


    @dezren39 · 2026-09-30T07:20:00Z

    Integration pass. Placed in the DAG rooted at #181.


    @dezren39 · 2026-09-30T12:03:33Z

    Issues pass (2026-09-30, at 05c78b2).


    @dezren39 · 2026-10-28T21:35:32Z

    Triage verdict (task 2100, repo-health-nix-pilot): RELOCATED / SUPERSEDED IN PLACE — tracker stays open, but its work surface moved repos; no fix packet in this repo.

    Verified against current main at 224c7b738e88c7a8e5d406987a7d88257e229db2:

    Route: no small_fix packet was enqueued in dezren39/nix — there is no in-repo CI left to shard here. The open leaves (#135, #139–#143) presuppose the removed in-repo workflow and need a relocation pass to developing-today/code (per the cross-repo rule, that move should reference this tracker rather than spawning fresh per-repo issues). #95 stays open as the nix-side tracker until that relocation is decided/done; closing it here would orphan six open leaves.

  3. added
    queue/queuedHas a queued task in the Supabase agent queue
    kind/testTest coverage, test infrastructure, or test-only fixes
    priority/normalOrdinary priority (the default)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Agent note · filed

    Notes: Filed in the large mcpx filing batch of 2026-10-28 21:59–22:24Z and already carries a queued triage task — it is not orphaned. Verified live at intake: no merged PR references this issue and it was filed under an hour ago, so nothing can have resolved it yet. Acceptance criteria appended to the body from the issue's own stated scope; original body preserved verbatim above it.

    Risks: Tracker-level scope overlaps its sibling batch issues — the triage pass must dedupe against them before any implementation packet is written, and split rather than implement if the scope is multi-PR.

    Suggested approach: Triage first (task 2793): confirm the boundary against the sibling issues, then enqueue one scoped packet per deliverable slice.

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  6. 24 remaining items

  7. added and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: task 7766
    • Found via: monitor reconciliation
    • Task: Claim-eviction repair: the queue task for this issue is queued and unclaimed (its previous claim's lease expired without a complete/park/block), but the issue labels still said claimed. Labels reset to the truth: queue/queued + status/needs-triage. Checkpoint stage on the task: handed_off.
    • Expectation: a queue lane re-claims the task and resumes from the checkpoint stage named in the task progress.
  9. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · triaged — task 7766 complete

    Current-main verdict — tracker remains live; scope refreshed

    The old single-workflow premise has changed. Current CI work is spread across mcpx-test.yml, mcpx.yml, and the repo-wide formatter policy:

    Ordering / blockers: no external blocker. The tracked remaining work is #1994/task 8160 (measured shard balancing), #2836/task 8002 (Go lint debt), then #5151/task 8832 (plugin TypeScript lint/type-check). #1901 stays open as the umbrella until the measured-sharding work and its current acceptance are reconciled.

    Evidence: current workflows inspected at 52fd50d17e750ae84385b5b41414e886eccee95e; related issue and PR states checked fresh. No build/test run was applicable to this triage-only task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/lLarge: multi-day or open-ended; expect staged check-inskind/testTest coverage, test infrastructure, or test-only fixesmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions