Skip to content

mcpx testing: which tests an agent runs, and never the full suite locally #1966

Closed

Description

@dezren39

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/e2e lists only internal/api and internal/testsupport. The binary under test is built by a go build ./cmd/mcpx subprocess (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 (-run is 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 the go build subprocess reads are not tracked. So after editing, say, internal/runner, go test ./internal/e2e -run '^TestX$' would plausibly print ok ... (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 local go test ./...: its e2e line may have been cached.
  • -run is an unanchored regexp (go help testflag): -run TestEveryExec also 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 d956689 to e7457f6 #52, fixed in Phase 5: tag event bodies must travel with the event #182). newEnv's cleanup now runs stop --all, which reads the daemon-<key>.json info files instead of recomputing the socket path, and logs a failure instead of discarding it (internal/e2e/e2e_test.go:71-85). newEnv also sets MCPX_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: testsupport still leaves its mcpx-bin-* and mcpx-fake-* temp dirs behind on every test-binary run (build.go:27,57, os.MkdirTemp with 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) -- but e2e_test.go holds 129 of the 209 tests.
  • No test recipe exists anywhere: the repo-root justfile has no mcpx recipes, pkgs/mcpx has no justfile; scripts/ has only bench.sh and stress.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:

  1. Never run go test ./... or an unfiltered go test ./internal/e2e locally, 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 from d956689 to e7457f6 #52). Phase 5: tag event bodies must travel with the event #182 removed that one. The cost reason remains.
  2. Always -count=1 for e2e (stale green above). For unit packages the cache is correct and welcome.
  3. 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).
  4. 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.
  5. 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.
  6. Stopgap until chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to e7457f6 #52 lands (unverified): newEnv passes os.Environ() through, and the setting's derived env name is MCPX_AUTOSTART_IDLE_EXIT (internal/settings/schema.go:225-230), so exporting e.g. MCPX_AUTOSTART_IDLE_EXIT=2m before a T2 run should bound any leak to two minutes. Check that no e2e test asserts the default first. Done in Phase 5: tag event bodies must travel with the event #182: newEnv sets MCPX_AUTOSTART_IDLE_EXIT=60s itself (internal/e2e/e2e_test.go:68,92), so there is nothing to export.
  7. 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 -race into 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]:

  1. git diff --name-only <base>...HEAD -- pkgs/mcpx (default base: origin/main).
  2. Each edited .go file -> its package -> T1. If exported identifiers changed, add importers (T1b).
  3. For e2e: pull the names the diff touches -- setting paths in internal/settings/wire.go, op names in internal/api/ops*.go, flag names in internal/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).
  4. Print the commands and an estimate from per-test durations (from CI's go test -json artifact, Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95). Dry-run by default; --run executes them with -count=1.
  5. 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

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

  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#127 (verbatim, oldest first):


    @dezren39 · 2026-09-30T07:21:15Z

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


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

    Issues pass (2026-09-30, at 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.

  3. added
    kind/testTest coverage, test infrastructure, or test-only fixes
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 29, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Agent note · state: filed

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: repo-fleet-monitor run 2026-10-28 19:19 CDT
    • Found via: monitor reconciliation (intake enrichment, protocol §5)
    • Task: intake enrichment — acceptance criteria, normalized labels, this review
    • Expectation: effort/m; done when the AC section, labels, and this note are on the issue
    • Links: n/a
  5. added
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  6. 71 remaining items

  7. added
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Oct 30, 2026
  8. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up (resume)

    • Agent: queue worker (repo-worker-code), lane D
    • Session / ID: queue task 3263, attempt 2
    • Found via: queue claim — resume; PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644 is this task's canonical PR.
    • Task: Resume check per the previous checkpoint: CI run 41050416067's stress: PR changed tests job is STILL queued (created 11:45Z, runner starvation — no head of this PR has ever had a completed mcpx CI run). Codex for head e7f61867 is completed with no unresolved findings (round-5 replies posted 11:44Z). Additional local verification this pass (laptop worktree at e7f61867, clean): affected-tests.py compiles (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).
    • Expectation: still parked — merging a test-selection PR whose own stress check has never run waits for that check, per the previous lane's recorded judgement. Resume condition unchanged: run 41050416067 (or a fresh run for e7f61867) green → merge PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644 → complete task 3263. Everything else is verified and dispositioned.
    • Links: PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644 · CI run 41050416067 · queue task 3263
  9. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Oct 30, 2026
  10. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up (resume)

  12. added a commit that references this issue on Oct 30, 2026
  13. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — close-out for #1966

    • Agent: queue worker (repo-worker-code), Lane A shift (resume of the parked pass)
    • Session / ID: queue task 3263 · completed 2026-10-30
    • Result: PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644 merged (8fc11992). Delivered: the adopted test-tier policy T0–T4 + rules 1–7 as pkgs/mcpx/TESTING.md; the Approach C affected-tests selector pkgs/mcpx/scripts/affected-tests.py (dry-run by default, --run executes, empty selection prints "no e2e selected; CI runs the full suite" and never falls back to the full suite); just mcpx-test-affected in the root justfile.
    • Open questions decided (in PR + TESTING.md): recipe location = root justfile; T2 budget = both forms (15-test cap + printed time estimate); stale-cache hypothesis CONFIRMED by experiment (cached e2e green survived an edit to a package the test binary doesn't import — rule 2, always -count=1 for e2e, stands).
    • Verification on final head 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).
    • Downstream: mcpx testing: a contributor test page agents load, kept out of the user-facing AGENTS.md #1976's contributor page (PR docs(mcpx): contributor test page + nested agent pointer (#1976) #2666) already merged and consumes this policy text.
    • Links: PR mcpx testing: adopt agent test tiers + affected-tests selector (#1966) #2644

    Closing this issue as completed — the decision and the tooling it asked for are both landed.

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

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/mMedium: expected under 1 day of workenhancementNew feature or requestkind/testTest coverage, test infrastructure, or test-only fixesmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions