Repository navigation
Description
Relocated from dezren39/darwin-nix#127 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: #99
Summary
Decide which tests an agent working on mcpx runs, and when, so that no agent runs the whole suite as a habit. The rule: an agent runs the smallest set that covers its change -- the edited packages' unit tests, a named handful of e2e tests, and the guard tests for any registry it touched -- and leaves the full suite to CI (#95). This issue decides the tiers, how an agent finds the tests that touch a change, what is never run locally, and how a CI failure is reproduced. The page that tells agents all this is #131.
Original notes
- testing
- importantly, as we add more testing agents should not run every single test basically ever. below documents a rough idea of a ci job design/policy, the main priority is being short as possible.
(The second line is also the joint strategy's framing in #91 and the CI tree's preamble in #95. This issue owns its agent-side half: what an agent runs locally. The CI half -- keeping the full run short -- is #95.)
Current state (re-checked at 05c78b2)
Per-package cost, CI run 36693030957 at 05c78b2 (ubuntu-latest, go test ./... -count=1, step total 148 s, the rest is compilation). The d0b7e01 run 36673523537 had e2e at 100.7 s of a 135 s step; e2e grows with every test.
| Package | Time | Notes |
|---|---|---|
internal/e2e |
108.9 s | 209 tests, serial (no t.Parallel() anywhere), each drives the real binary |
internal/tui |
8.6 s | |
internal/pool |
1.6 s | |
internal/events, internal/mcpserver |
~1.0-1.3 s each | |
| every other package | < 0.6 s | 25 packages |
nix build .#mcpx |
70 s | re-runs 18 unit packages in checkPhase (package.nix:73-82) |
Facts that shape the policy:
- e2e sees every change, through the binary, not through imports.
go list -test -deps ./internal/e2elists onlyinternal/apiandinternal/testsupport. The binary under test is built by ago build ./cmd/mcpxsubprocess (internal/testsupport/build.go:54-74). So Go's own dependency graph cannot say which e2e tests a change affects -- by that graph almost no change affects e2e, and in reality every change does. - Stale green risk (not reproduced; nobody has checked it yet).
go help test: results are cached for runs using only cacheable flags (-runis one), and a cached result is reused while the test binary, the env vars it reads and the files it opens are unchanged. The files thego buildsubprocess reads are not tracked. So after editing, say,internal/runner,go test ./internal/e2e -run '^TestX$'would plausibly printok ... (cached)without building the new binary. CI is safe (-count=1,mcpx.yml:65). Any local e2e run must use-count=1. The same applies to a localgo test ./...: its e2e line may have been cached. -runis an unanchored regexp (go help testflag):-run TestEveryExecalso matches any longer name. Use-run '^(TestA|TestB)$'; subtests-run '^TestA$/^case$'.- Local e2e runs no longer leak daemons (chore(deps): bump build/buck/preludes/buck2-prelude from
d956689toe7457f6#52, fixed in Phase 5: tag event bodies must travel with the event #182).newEnv's cleanup now runsstop --all, which reads thedaemon-<key>.jsoninfo files instead of recomputing the socket path, and logs a failure instead of discarding it (internal/e2e/e2e_test.go:71-85).newEnvalso setsMCPX_AUTOSTART_IDLE_EXIT=60s(:68,:92), so a daemon that escapes an interrupted run dies within a minute instead of 4h.TestEnvCleanupStopsTheDaemonItStarted(:2630) guards it and was falsified by reverting the fix. What remains:testsupportstill leaves itsmcpx-bin-*andmcpx-fake-*temp dirs behind on every test-binary run (build.go:27,57,os.MkdirTempwith no removal). - Existing file split in e2e helps a little:
exec_test.go,settings_test.go,v1ops_test.go,proto_test.go,consumer_test.go,runtime_test.go,logs_test.go,multiconfig_test.go,unread_test.go(Phase 2: delegated Manage cannot add members #171) -- bute2e_test.goholds 129 of the 209 tests. - No test recipe exists anywhere: the repo-root
justfilehas no mcpx recipes,pkgs/mcpxhas no justfile;scripts/has onlybench.shandstress.sh(manual, real servers). - History: Update Rust crate axum to 0.6.20 #65 was merged with its own check red and unread. Whatever the local policy, reading the PR's CI result before merging is part of it.
- In practice, integrators do run the full suite locally, repeatedly. PR bodies: Update Rust crate bacon to 2.14.0 #69 and Update Rust crate globwalk to 0.9.0 #70 "Suite 2× clean", Phase 2: delegated Manage cannot add members #171 "Suite 3× clean before my change and 2× after", Phase 5: tag event bodies must travel with the event #182 "Suite 3× clean" plus a daemon count across the runs. The merge protocol the integrating agents follow asks for this. It is the only repeat-run flake detection mcpx has until Update Rust crate actix-files to v0.6.10 [SECURITY] #130's CI jobs exist. The policy below therefore has to say who runs the full suite (the integrator, before merging), not only who does not.
Proposed policy (the tiers)
| Tier | When | Command | Typical cost |
|---|---|---|---|
| T0 format/vet | always, touched packages | gofmt -l <files>; go vet ./internal/<pkg>/ |
< 2 s |
| T1 unit | always, each package with an edited .go file |
go test -count=1 ./internal/<pkg>/ |
< 2 s each (tui 8.6 s) |
| T1b dependents | only when an exported identifier changed | the same, for packages that import it (go list -f '{{.ImportPath}} {{join .Imports " "}}' ./... reverse lookup) |
seconds |
| T2 targeted e2e | when behaviour visible through the binary changed | go test -count=1 ./internal/e2e -run '^(TestA|TestB)$' |
~0.5 s/test average + one binary build; budget ~15 tests / ~30 s |
| T3 guards | when a registry was touched (flag, /v1 op, setting, default constant) |
the guard for that registry (list in #129), e.g. -run '^TestEveryExecFlagChangesSomething$' |
seconds |
| T4 build | only when package.nix, go.mod/go.sum or vendorHash changed |
nix build .#mcpx |
70 s |
| Full suite | never locally for a routine change; CI on push (#95). The integrator merging a PR is the exception (rule 7) | -- | ~110 s of serial e2e plus compilation, and growing |
Rules around the tiers:
- Never run
go test ./...or an unfilteredgo test ./internal/e2elocally, unless the user asks for it by name or you are the integrator merging (rule 7). The reason is cost: ~110 s of serial e2e plus compilation, paid again on every run, and the number grows with every e2e test. There used to be a second reason: each run left daemons alive for up to 4 h (chore(deps): bump build/buck/preludes/buck2-prelude fromd956689toe7457f6#52). Phase 5: tag event bodies must travel with the event #182 removed that one. The cost reason remains. - Always
-count=1for e2e (stale green above). For unit packages the cache is correct and welcome. - Push, then read CI. The full run is CI's job; an agent must read the result of the PR's check before declaring done or merging (Update Rust crate axum to 0.6.20 #65).
- Harness changes (
internal/testsupport/**,newEnv, the fake servers) affect every e2e test: run a small sample locally (^(TestNoOrphanProcessesAfterStop|TestEnvCleanupStopsTheDaemonItStarted|TestEveryDeclaredRouteAnswersOverTheSocket)$) and let CI run the rest. - Reproducing a CI failure: re-run only the failing test(s), by exact name, with the same run seed and shuffle order the CI shard printed (seed design and env var name: Bump golang.org/x/crypto from 0.10.0 to 0.17.0 in /sources/graph #93; shard layout: Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95). If it does not reproduce once, it is treated as a possible flake and handled by Update Rust crate actix-files to v0.6.10 [SECURITY] #130 (
-count=N,-race), not by re-running the whole suite. Stopgap until chore(deps): bump build/buck/preludes/buck2-prelude fromDone in Phase 5: tag event bodies must travel with the event #182:d956689toe7457f6#52 lands (unverified):newEnvpassesos.Environ()through, and the setting's derived env name isMCPX_AUTOSTART_IDLE_EXIT(internal/settings/schema.go:225-230), so exporting e.g.MCPX_AUTOSTART_IDLE_EXIT=2mbefore a T2 run should bound any leak to two minutes. Check that no e2e test asserts the default first.newEnvsetsMCPX_AUTOSTART_IDLE_EXIT=60sitself (internal/e2e/e2e_test.go:68,92), so there is nothing to export.- The integrator's pre-merge run is the exception, and it is deliberate. Whoever merges a PR runs the full suite locally more than once (current practice: 2-3 times, see Current state). Until Update Rust crate actix-files to v0.6.10 [SECURITY] #130 moves repeat runs and
-raceinto CI, that is the only place mcpx looks for order- and timing-dependent flakes. Once Update Rust crate actix-files to v0.6.10 [SECURITY] #130's per-PR repeat job and nightly stress job exist, this step can drop to a single run or leave the local routine entirely.
Approaches: how an agent finds the tests touching a change
A -- judgment and grep (least recommended)
The agent reads its diff, greps internal/e2e for the feature's name, and picks tests. Costs nothing to set up. Risks: every agent picks differently; tests named for a behaviour rather than the flag or op (most of e2e_test.go) are missed; and an uncertain agent falls back to "run everything", which is the behaviour this issue exists to stop. Acceptable only as the documented fallback when C selects nothing.
B -- a maintained map from package/feature to tests
Split e2e_test.go by feature and keep a map (testdata/test-map.yaml or comments) from internal/<pkg> and feature names to e2e files or regexes. Deterministic and readable. Costs: the map drifts as soon as a test is added under the wrong key, splitting e2e_test.go is a large mechanical diff, and the feature inventory is owned by #97 -- a second map here would compete with it. Worth doing only if #97's inventory becomes that map (then C can read it).
C -- a selection script with a just recipe (recommended)
scripts/affected-tests.sh (or go run ./internal/testsupport/affected, to avoid bash portability questions) plus a recipe such as just mcpx-test-affected [base]:
git diff --name-only <base>...HEAD -- pkgs/mcpx(default base:origin/main).- Each edited
.gofile -> its package -> T1. If exported identifiers changed, add importers (T1b). - For e2e: pull the names the diff touches -- setting paths in
internal/settings/wire.go, op names ininternal/api/ops*.go, flag names ininternal/cli, CLI command names, fake-server tool names -- and select e2e test functions whose bodies mention them; add the guard tests for any registry file touched (T3). - Print the commands and an estimate from per-test durations (from CI's
go test -jsonartifact, Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95). Dry-run by default;--runexecutes them with-count=1. - If nothing is selected, say "no e2e selected; CI runs the full suite" -- never fall back to all.
Recommended because it gives every agent the same answer for zero context cost, it is checkable (CI can later compare its selection with the tests that actually failed, i.e. measure the miss rate), and a miss costs one CI round-trip rather than a bug, since CI still runs everything. The same selector can later feed a PR fast lane in #95 if that tree wants one.
Open questions
- Confirm the stale-cache hypothesis once, in a scratch worktree (safe to run now that Phase 5: tag event bodies must travel with the event #182 fixed chore(deps): bump build/buck/preludes/buck2-prelude from
d956689toe7457f6#52): edit a non-imported package, rungo test ./internal/e2e -run '^TestX$'twice without-count=1, look for(cached). - Where the recipe lives: repo-root
justfile(the nix repo's) vs a newpkgs/mcpx/justfilevs onlyscripts/. A root recipe is discoverable; a package-local one keeps mcpx self-contained. - The T2 budget: a count (~15 tests) or a time (~30 s)? Time is what matters; count is what an agent can see before running.
- The env var the run seed arrives in, and how a shard prints it (Bump golang.org/x/crypto from 0.10.0 to 0.17.0 in /sources/graph #93, Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95).
- Should T0 include the linters Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95 adopts (e.g.
golangci-lint run ./internal/<pkg>/...)? Probably yes once they exist.
Ease · Impact · Related
Ease S for the policy text; M with the selection script (C) · Impact high
Depends on / related: #52 (closed, fixed in #182; "never locally" now rests on cost alone), #93 (run seed, reproduction), #95 (full suite, shards, go test -json timings), #97 (feature inventory, if B's map is ever built), #65 (read the check before merging), #91; siblings #131 (states this policy), #129 (T3 list), #130 (non-reproducible failures; moves the integrator's repeat runs into CI).
Security/auth deferred; see tracker.
Acceptance criteria
- The test-tier rule is decided and recorded: an agent runs the smallest set covering its change (edited packages' unit tests, a named handful of e2e tests, guard tests for any registry it touched) and never the full suite locally as a habit; the full suite is CI's job.
- How an agent finds the tests that touch a change is defined (mechanism or documented procedure).
- How a CI failure is reproduced locally is defined.
- The decisions are handed to the contributor page (mcpx testing: a contributor test page agents load, kept out of the user-facing AGENTS.md #1976) in a form it can carry verbatim.
- The work described in the Summary/Scope above is delivered via a branch + PR (no direct push to main); verification runs in GitHub Actions CI, not on the sandbox.
- Evidence (the recorded rule) is recorded on this issue before it is closed.
Activity
developing-today commentedon Oct 28, 2026
CODE-891
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#127 (verbatim, oldest first):
@dezren39 · 2026-09-30T07:21:15Z
Integration pass. Placed in the DAG rooted at #181.
omasnapupstream repository to flake inputs #129 Update Rust crate actix-files to v0.6.10 [SECURITY] #130 Update Rust crate tracing-subscriber to v0.3.20 [SECURITY] #131@dezren39 · 2026-09-30T12:03:31Z
Issues pass (2026-09-30, at 05c78b2).
d956689toe7457f6#52 is closed (Phase 5: tag event bodies must travel with the event #182:stop --allcleanup through the info files,MCPX_AUTOSTART_IDLE_EXIT=60s, and a leak test falsified by reverting the fix). "Never run the full suite locally" now rests on cost alone: 148 s CI step, e2e 108.9 s, run 36693030957. That cost grows with every e2e test. I rewrote rule 1 and the Full suite row to say so.TestEnvCleanupStopsTheDaemonItStartedto the harness-change sample (rule 4). Re-citedbuild.go:54-74,mcpx.yml:65and the e2e counts (129 of 209 tests are ine2e_test.go). The per-package table is updated to 05c78b2.@dezren39 · 2026-09-30T12:09:49Z
Issues pass (2026-09-30, cross-worker note). #134 now defines run modes, including
inline(the daemon runs in-process for the test and dies with it). Worth considering as the default for an agent's local e2e runs under this issue's rules: it cannot leave a daemon behind or reach the developer's own daemon, which removes two of the reasons an agent's local run is risky, leaving only cost. Decide here; #134 owns the mode itself.dezren39 commentedon Oct 29, 2026
Intake review
🤖 Agent note · state: filed
71 remaining items
dezren39 commentedon Oct 30, 2026
🤖 Agent note · picked up (resume)
stress: PR changed testsjob is STILL queued (created 11:45Z, runner starvation — no head of this PR has ever had a completed mcpx CI run). Codex for heade7f61867is completed with no unresolved findings (round-5 replies posted 11:44Z). Additional local verification this pass (laptop worktree ate7f61867, clean):affected-tests.pycompiles (py_compile) and runs, emitting sane tiered output; the run's job list (basic/stress/vendorHash) was scheduled from this PR's own modified workflow, proving the workflow file parses and its job graph resolves; the PR diff touches no Go source (workflow, TESTING.md, selector script, root.just only).e7f61867) green → merge PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644 → complete task 3263. Everything else is verified and dispositioned.dezren39 commentedon Oct 30, 2026
🤖 Agent note · picked up
queue/3263-1966-test-tiers-selectordezren39 commentedon Oct 30, 2026
🤖 Agent note · picked up (resume)
e7f61867— mcpx workflow run 41050416067 SUCCESS, stress (PR changed tests) SUCCESS, Codex review completed with no unresolved findings. The 5 review threads still marked unresolved in the UI were all already dispositioned (fixed in round-3 commits with replies on thread); marked them resolved clerically. Merging now; close-out evidence follows.Merge pull request #2644 from developing-today/queue/3263-1966-test-t…
dezren39 commentedon Oct 30, 2026
🤖 Agent note · done — close-out for #1966
8fc11992). Delivered: the adopted test-tier policy T0–T4 + rules 1–7 aspkgs/mcpx/TESTING.md; the Approach C affected-tests selectorpkgs/mcpx/scripts/affected-tests.py(dry-run by default,--runexecutes, empty selection prints "no e2e selected; CI runs the full suite" and never falls back to the full suite);just mcpx-test-affectedin the root justfile.-count=1for e2e, stands).e7f61867: mcpx workflow run 41050416067 SUCCESS (full suite via CI, per rule 3/7 — no local full suite run); stress (PR changed tests, race/shuffle/repeat) SUCCESS; Codex review completed with no unresolved findings (all rounds dispositioned — fixes in round-2/round-3 commits with replies on thread; the last 5 threads were marked resolved on this resume).Closing this issue as completed — the decision and the tooling it asked for are both landed.