Skip to content

mcpx testing: a failing-first test for every bug, and a review of recent fixes #1969

Closed

Description

@dezren39

Relocated from dezren39/darwin-nix#128 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

Two parts. Policy: every bug observed from any source gets at least one new test, in the same PR as the fix, that fails without the fix -- when that is cheap -- and the test covers the bug's permutations, not just the one input that tripped it. Review: audit the recently fixed mcpx bugs against that policy, and file the missing tests. The checklist that enforces it (PR template vs CI check) is decided here too.

Original notes

  • testing
    • agents should make at least 1 simple, new test for every bug observed from any source, when cheap to do so-- aggresively test many permutations.
      • you can absolutely rely on fuzzing anywhere you can fit it and you should whenever you can-- but you must use a single seed per-run to generate all inputs. (this could eventually be discussed with careful consideration.) it should be appended into a test tracking structured file in the repo and it can be committed. ideally main branch commit's seed or pr merge seed should make it back into file eventually. to prevent churn, maybe ci appends the seed into a gha var (race/overwrite risk?), or attached as artifact to each workflow in init job before tests begin-- a collector job can scoop them up sometime later. once a day/week/on-release/every-100-commits. im thinking touch tests/seeds/ or uv has a seed generate job. anyways ci it should end up in that dir, then as an artifact if gha and main branch, then passed into the test matrix. if uv test without arg- warn, generate and write it yourself pass it to all tests, if some other testing method there should not be a static seed, throw for empty or setup should make the seed. avoid changing how seed generates data. im not sure if a convenient uuid with time, v7?, is valid, but as long as its enough entropy whatever works. either put nothing in their or compact json, consider seeds/v1/id/<first 2 char>/<--symlink: seeds/yyyy/mm/dd/ avoiding conflicts is important.

The seed bullet is quoted here because it is the second half of this policy ("aggresively test many permutations" is how, "a single seed per-run" is the constraint). Its design -- seed format, storage under tests/seeds/, GHA artifacts, the collector job, uv -- is owned by #93 and is not decided here. This issue only commits to: permutation generators in regression tests take their randomness from the run seed #93 defines, never from their own clock or a hardcoded constant.

Current state

  • No Fuzz*, property tests, math/rand or testing/quick anywhere; permutations today are hand-written table tests (e.g. TestEveryExecFlagChangesSomething, internal/e2e/e2e_test.go:2877, four rows).
  • No PR template (.github/ holds only workflows/). Agents open PRs with gh pr create --body ..., which does not apply a template, so a template checkbox alone would not reach them.
  • The best existing example of the policy is PR chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to ba020c6 #62: "The new test exercises the script path. Verified it fails without the fix and passes with it." -- and it names why the old test missed it ("only the first direction was tested"). Two later PRs are as good or better. Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 found that its own guard's first --typecheck assertion matched output present either way; the table's without-flag check caught it. Phase 5: tag event bodies must travel with the event #182 reverted its cleanup fix to watch the new leak test fail before trusting it.

First-pass review of recent fixes (from PR bodies and changed-file lists, merged PRs through #182 at 05c78b2; to be confirmed)

Bug Found by Fix Regression test in the fix PR? Fails without fix? Gap / follow-up
#35 two tests passed on macOS only CI (first Linux run) PR #35 The tests were wrong; fixed (daemon/paths_test.go +30, e2e_test.go +19); CI now installs deno/bun/node n/a The class (OS-dependent assumptions) is still unguarded: CI is ubuntu-only, so a Linux-pass/macOS-fail is now invisible -> flake/OS sibling #130, #97, #95
#65 registry test reached the internet CI timeout PR #65 Yes, structurally: newEnv sets MCPX_REGISTRY_URL to a stub (e2e_test.go:57) not stated Nothing fails when a test dials out; unit tests outside newEnv are not covered -> guard in #129
#74 exec flags accepted and ignored agent, by accident (#73 noticed --launcher) PR #75 Yes: TestEveryExecFlagChangesSomething (e2e_test.go:2877), asserts with and without the flag yes, and the guard falsified itself: the first --typecheck assertion looked for the bad name, which appears either way (as a runtime error without the check); the without-flag half of the row failed it. The row now asserts the checker's own refusal, does not type check, which cannot appear without the check The table has 4 rows (--export, --launcher, --prefix, --typecheck) for 23 flags on the shared exec/run flag set (internal/cli/commands.go:435-481) and does not enumerate that set; --no-launcher, --allow-repeat, --no-capture-console fixed in #75 have no row -> #129
#52 e2e leaks daemons agent (while on #44) PR #182 yes: TestEnvCleanupStopsTheDaemonItStarted (e2e_test.go:2630) yes, stated: "Reverting the cleanup makes it fail on the relocated-fallback path", naming the leaked pid and socket model example. Still open: TestNoOrphanProcessesAfterStop (e2e_test.go:534) covers children after an explicit stop, and the new test covers one env; there is no suite-wide sweep, and testsupport temp dirs still leak (build.go:27,57) -> guard in #129
flaky client test, "one run in three panicked" repeated full-suite runs (the lead's third run) PR #46 (fake fixed) unclear: mcpclient/era_test.go +15 no deterministic repro recorded Only a repeat run finds this shape -> #130
data race in tasks.Store.Start -race, ~1 run in 5 (#56's report) PR #61 no test can show it except under -race, which CI does not run -- -> #130 (-race in CI)
inline script could not reach the daemon agent question PR #62 yes, e2e +22 yes, stated model example
--typecheck dropped on exec agent PR #66 yes, codegen_test.go +17, e2e +33 not stated
concurrent /v1/exec runs shared one script.ts; cleanup deferred a captured value and left every program behind agent, measuring --typecheck cost PR #69 yes, runner/unique_test.go (deleted by #70 when names became content hashes) not stated; verified with 8 concurrent runs against the binary the replacement's coverage is #70's
two configurations overwrote each other's generated client (introduced by #69) agent PR #70 yes: TestTwoConfigurationsDoNotShareAGeneratedClient (e2e_test.go:2826), runner/workdir_test.go not stated; measured on the binary, 7/12 failing before, 0/12 after #69's tests covered one configuration only: a fix that shipped a bug one PR later. The permutation rule (5) exists for this
concurrent runs of one script executed each other's launcher agent PR #73 yes: TestConcurrentRunsOfOneScriptKeepTheirOwnLauncher (e2e_test.go:2938) not stated; 1/6 correct before, 6/6 after, on the binary third instance of "two runs share a generated path" in three PRs (#69, #70, #73) -> a class; candidate guard in #129
schema cache not invalidated when an entry gains a field agent PR #58 no; version bumped, "the trap itself remains" -- tracked in #78; needs a test that an old-shape cache is rejected
defaults guard missed 5*time.Minute (gofmt spelling) agent PR #56 guard rewritten pattern-based not stated a guard needs its own negative test -> #129
vendorHash broken by a Renovate bump (#29) human, after merge PR #34 not a Go test: CI build job added -- right guard for that class (build in CI)
plugin read fields that did not exist agent PR #12 no: only tui_test.go changed; plugin/opencode/mcpx/daemon.test.ts is never run by CI -- #95 (run plugin tests), #47/#48
WithExtras dropped Notify; --grep could not match an event name agent PR #46 likely (logstore_test.go +20); confirm for WithExtras not stated
settings declared and never read (24 wired, 3 deleted; script.env dead twice); logging capability declared and undelivered; subscribe/listChanged gated per server, not per connection; POST /v1/diagnose session and GET /v1/log?chain= filters dropped agent audit PR #171 yes: the consumed_test.go guard (internal/settings/consumed_test.go:73,145,209), nine effect tests in internal/e2e/unread_test.go (nine of the 24 settings it wired), plus logs_test.go, proto_test.go, server_test.go, registry_test.go changes not stated for the tests. The listChanged over-correction was caught against the binary, not by a test The /v1/log?chain= e2e test had hidden the bug by passing --level debug, so it was a test that could not fail. The new guard proves a setting is read, not that reading it matters: registry.pageSize passed it (#179) -> #129 (#181 collision 15)

The policy (proposed wording for the contributor page, #131)

  1. Sources that count: user reports, CI failures (including ones that "went green on retry"), an agent noticing something while doing other work, review comments, flaky runs, -race reports, and bugs in tests themselves (fix(deps): update rust crate axum to 0.6.20 #35).
  2. Where: the test lands in the same PR as the fix. A fix without a test says why in the PR body (No test: <reason>).
  3. Red first: the test must fail without the fix. Either commit the test before the fix (reviewable in history), or state "fails without the fix" in the PR body as chore(deps): bump build/buck/preludes/buck2-prelude from d956689 to ba020c6 #62 did. Better still, revert the fix and quote the failure, as Phase 5: tag event bodies must travel with the event #182 did. "Fails without the fix" means the assertion discriminates, not just that the test runs. Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75's first --typecheck assertion matched text present with or without the flag and would have passed either way.
  4. "Cheap" means roughly: a unit test, or an e2e test that adds < ~2 s. When it is not cheap (needs a real server, an OS CI does not have, minutes of setup), file an issue labelled testing with the reproduction instead of dropping it.
  5. Permutations, aggressively. Test the bug's shape: a Go table test over the dimensions the bug lives in (runtime deno/bun/node x snippet/file x daemon/inline x socket/TCP is mcpx's usual cross-product; add "one configuration vs two" and "one run vs concurrent runs" after Update Rust crate bacon to 2.14.0 #69/Update Rust crate globwalk to 0.9.0 #70/Update Rust crate libtest-mimic to 0.8.0 #73), or a generator seeded from the run seed (Bump golang.org/x/crypto from 0.10.0 to 0.17.0 in /sources/graph #93). Keep the cross-product in unit or in-process tests; in e2e, pick the rows only the binary can prove, because every e2e row is a subprocess and CI is budgeted (Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95).
  6. Name it for the behaviour, not the issue number (TestInlineScriptReachesTheDaemon, not TestIssue62), and put the issue number in a comment.
  7. If the bug is one of a class (the same shape has shipped before, or a declaration and its use live apart), the fix also adds or extends a guard test -- Add omasnap upstream repository to flake inputs #129.

Approaches: how the policy is enforced

A -- a PR template checkbox (least recommended alone)

.github/pull_request_template.md with "[ ] regression test added / fails without the fix". Cheap. But the template is repo-wide (this is a whole nix config repo, most PRs are not mcpx), GitHub supports per-template files only via ?template= URLs, and gh pr create --body -- how agents open PRs -- skips templates entirely. A checkbox is also self-attested.

B -- the checklist in the contributor page and the review step

The rule lives in the contributor page (#131), and whoever reviews (human or review agent) checks it. Reaches agents where they actually read instructions, costs nothing in CI. Still self-attested, and misses PRs nobody reviews.

C -- B plus a cheap CI check (recommended)

B, plus a CI step (#95) on PRs touching pkgs/mcpx/**: if the PR is a fix (title starts with fix, body has Fixes #/Closes # on a bug issue, or a label says so) and changes no *_test.go / *.test.ts, the step fails unless the body contains No test: <reason>. It does not prove the test fails without the fix; that stays a review item (an optional later step could run the PR's new tests against the merge base and expect failure). Recommended because it catches the common miss for seconds of CI, leaves an escape hatch that is visible in history, and does not depend on templates agents never see.

Tasks

Open questions

  • Is "fails without the fix" proven by commit order, by PR-body statement, or by a CI job that runs new tests against the merge base? The last is the only non-attested option and the most expensive.
  • Does a bug found and fixed inside the same unmerged PR count? (Proposed: yes if it reached a pushed commit or CI.)
  • Label for flaky-found bugs: reuse testing + bug, or add a flaky label? (Label set is fixed today.)

Ease · Impact · Related

Ease S (policy) / M (review + missing tests) / S (CI check) · Impact high
Depends on / related: #93 (run seed, generators, the quoted seed bullet), #95 (the CI check, plugin tests), #97 (integration inventory), #35, #52 (closed, fixed in #182), #65, #74 (closed, fixed in #75), #75, #78, #46, #56, #58, #61, #62, #66, #69, #70, #73, #171, #179 (the setting #171's guard passed), #182; siblings #127, #129, #130, #131.

Security/auth deferred; see tracker.

Acceptance criteria

  • The failing-first policy is recorded: every bug gets at least one new test in the same PR as its fix, failing without the fix (when cheap), covering the bug's permutations — and the enforcement checklist (PR template vs CI check) is decided and in place.
  • Recently fixed mcpx bugs are audited against the policy, with the audit result recorded on this issue.
  • Missing tests the audit finds are filed (issues or PRs) and linked here.
  • 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 (audit list and links) 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#128 (verbatim, oldest first):


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

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


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

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

    The review list is checked against gh pr list --state merged through #182.


    @dezren39 · 2026-09-30T12:27:17Z

    Issues pass (2026-09-30, architect follow-up). Related: #74 annotated "(closed, fixed in #75)".

  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
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  6. 22 remaining items

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · claimed

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  10. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 3, 2026
  11. added a commit that references this issue on Nov 3, 2026
  12. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  13. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
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 workkind/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