Repository navigation
Description
Relocated from dezren39/darwin-nix#177 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: #181
Purpose
Six open issues are filed separately and are the same bug six times: something mcpx
declares does not do what the declaration says. A doc promises a capability the binary
does not have (#102, #159). A flag is accepted and is inert (#108). A function is built,
tested and never called (#116). An env var is honoured and declared nowhere (#172). A
concept — "a script file" — is resolved one way locally and another way remotely (#106).
Every one of them was found by a human, by hand, after shipping: reading --help, reading
a flag definition, trying a documented example. None was found by a test, because no test
was watching the seam. That is the argument for treating them as one item: fixing the six
instances leaves the seam unwatched and the seventh arrives on schedule.
Done looks like: each instance fixed, and — the part that matters — every surface in
mcpx that declares a set of things has a test that walks that declaration and proves each
member does something observable. The inventory below is the checklist for the second half.
The class, stated
A promise-and-behaviour disagreement is any place where mcpx publishes a set — commands,
flags, routes, tools, settings, environment variables, documented capabilities — and a
member of that set does not do what publishing it implies. The failure mode is specific and
nasty: it is indistinguishable from success. A flag that does nothing parses cleanly and
exits 0. A documented feature that does not exist reads exactly like one that does. An
unread setting appears in mcpx settings. The user's evidence and the code's behaviour are
both silent, so the bug survives until somebody tries the thing for an unrelated reason.
This is distinct from a plain defect, where something is attempted and produces a wrong
answer. Here nothing is attempted at all, and the surface reports that everything is fine.
Instances
- Bump build/buck/preludes/buck2-prelude from
d956689tof12c8d1#102 an adapted binary is not callable from a script, though the docs say it is —docs/story.mdsays an adapted CLI "is callable from a script"; adapter tools are attached asmcpserver.Extra(internal/cli/serve.go:346,391) andinternal/codegennever seesExtra, so they reachtools/listover/mcpand stdio and appear in no generated client, nomcpx ls, no script. Confirmed on 8001f23:rg Extra internal/codegenreturns nothing. - Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 exec should run any shebang, and resolve file scripts the same way local or remote — half of this issue belongs to this class and half does not. The class half:
filemeans two different things. The CLI resolves a script name against a search path (resolveScript,internal/cli/scripts.go:254), while/v1/execpassesreq.Filestraight through (internal/execsvc/execsvc.go:326) torunner.Options.File, which doesfilepath.Absand runs it (internal/runner/runner.go:293), so a name that works locally is a path error remotely, anddocs/exec.md's "a remote run sends the script's text, not its path" is true of the CLI and false of the endpoint. The not-class half — shebang dispatch,script.interpreter,script.cwd— is new feature work and is tracked there on its own merits, not here. Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 is tracked natively under fix(id): make Nix E2E check reproducible #175 (runtimes and languages), which was created in the same session and claimed it first; it is listed here as a member of the class for its resolution half only. - Bump build/buck/preludes/buck2-prelude from
d956689tocb5dc60#108--runtime noderejects TypeScript that Node runs, because of one flag —internal/runner/runner.go:100still passes--no-warnings --experimental-strip-types; on Node 24 strip-types is the default (the flag enables nothing) and it is the wrong flag anyway (--experimental-transform-typesis the one that compilesenum). The flag is accepted by Node, exits 0, and is inert — which is why an "every flag we pass is accepted" check would not catch it. - chore(deps): update github.com/developing-today-forks/charm digest to 15e844d - autoclosed #116
diagnose.CallErroris built and tested but never called —internal/diagnose/diagnose.go:483, tested ininternal/diagnose/diagnose_test.go, zero non-test callers outside the package. Re-verified on 8001f23. Users getError: -32602 invalid paramswhere the function already produces the argument at fault and the date its schema changed. - Phase 2: membership, delegation, groups, and server config #172 44
MCPX_*variables the code reads that no setting declares — 64 distinctMCPX_*names are referenced in non-test code; the registry declares 152 settings; 44 referenced names are not among them. Re-counted on 8001f23. The counts depend on the allowlist and have never been reconciled with Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159's "about 49" — that is Phase 3: sync, blob gate, and relays #181 collision 17, owned by Phase 2: membership, delegation, groups, and server config #172, whose first deliverable is the allowlist itself. The inbound ones (MCPX_CONFIG,MCPX_CACHE_DIR,MCPX_ENDPOINT,MCPX_ALLOW_READ, …) are real user knobs with no config key, no flag, no/v1/settingsrow and no MCP tool. The outbound ones (MCPX_CWD,MCPX_CALL_ID,MCPX_HARNESS, …) are a contract with the script that is declared nowhere at all. - Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 the sources the reference would be generated from are incomplete, and several docs say what the code does not — the docs-side inventory of the same class:
Commands()is not the one command list,mcpx openapipublishes a removed server,docs/parity.mdis stale and nothing reads it,docs/opencode-plugin.md:406says the plugin "SetsMCPX_SESSION,MCPX_CALLand friends" when the plugin actually setsMCPX_SESSION_IDandMCPX_CALL_ID(plugin/opencode/mcpx-session.ts:670,677), and four documents claim mcpx is dependency-free whilego.modrequires five. Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 is tracked natively under Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96 (generated reference); it is listed here as a member of the class, not adopted, because GitHub allows one parent per sub-issue.
Added to this tracker in the 2026-09-30 issues pass, after the bodies above were written. Each is
the same shape; none changes the thesis.
- Phase 3: range read_flight buffers frames with no cap while more is set #178 the nix derivation's generated man page, completions and version are unverified, and one of them is not reproducible —
postInstallrunsmcpx manandmcpx completion <shell>and inspects neither;internal/cli/manual.go:335stampstime.Now()into a nix output;0.1.0is written in three build-relevant places (package.nix:19,:61,cmd/mcpx/main.go:18) that nothing keeps in agreement, andinternal/testsupport/build.go:63builds the e2e binary without the ldflags, so it will silently disagree on the first bump. - Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179
registry.pageSizeis read but unreachable, andnextCursoris parsed and dropped — the setting is consulted only insideif limit <= 0(internal/registry/registry.go:154-156), so it passed Phase 2: delegated Manage cannot add members #171's sweep while being inert to every ordinary caller. That is a hole in the newest guard in the repository and it is why this tracker's inventory is not finished when a surface is merely "guarded". - Phase 3: sync data phase batches are bounded by count, not bytes #180 re-verification of twelve reported findings, with corrected counts and two claims whose framing is wrong — the
reviewartefact for this class. One of its corrections stops a wrong fix (MCPX_SESSION); another disproves the static-binary blocker outright. - Phase 5: persisted incremental tag index #183
ForConfig's comment describes keying that was deliberately removed —internal/daemon/paths.go:79-82promises that editing a config produces a new daemon key;FingerprintConfig(:192) hashes the file paths. The code is right and the prose is wrong, on the one function whose semantics the fingerprinting rework was about. - Phase 3: test_an_interrupted_session_restarts_without_duplicates flakes under full-suite load #188
Resolved.PoolIDhashesHeadersin map order —internal/config/config.go:371-373ranges over a map while theEnvblock six lines above sorts its keys, so one configuration yields different pool ids across calls. The file already declares the rule and one branch breaks it. - Phase 4: joiner misses an update made without knowing its concurrent add #189 the opencode plugin sends field names the daemon does not read —
DaemonClient.callsendsarguments/sessionId/callId(plugin/opencode/mcpx/daemon.ts:471-473);/v1/callreadsargs/session/context(internal/api/ops.go:382,110-113). Arguments and session are both dropped, and the plugin README documents the call. Plusmcpx_execpassing no session and rendering anoutputfieldexecsvc.Resultdoes not have. - Serve stalls before printing node in test_world_module_shared_over_iroh_and_web_presentations #190 a wrong-case cwd on a case-insensitive volume keys separately from the on-disk spelling —
canonical()(internal/config/scope.go:188-201) resolves symlinks and keeps the given spelling, so one directory gets two scope keys. Over-isolation, never accidental sharing. - Phase 4: groups, encryption, and history #191 the degraded-scope log line says 'per-call' when the key is the directory —
internal/daemon/registry.go:761hard-codes "degraded to per-call" whilerepo/worktree/cwdfall back tocwd:<dir>, shared by every caller there. A diagnostic that reports the opposite of what happened. - Phase 4 fix: a joiner fetches the epoch it missed #192 a remote exec accepts and drops most exec flags — Update Rust crate petgraph to 0.6.4 - autoclosed #74's class on the path Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 did not cover.
- id: lib does not build with --no-default-features #193
/v1/eventsreplay drops everything past 256 events without a gap marker — documented as lossless, and silent when it is not.
Precedent (closed)
Each of these was the same class, each was fixed as a one-off, and each was found by a
person noticing rather than by a test failing.
- Bump checkmarx/ast-github-action from 2.0.19 to 2.0.20 #39 (merged) — mcpx declared MCP
samplingand the passthrough from fix(deps): update rust crate serde to 1.0.188 - autoclosed #31 was unreachable: the handler was installed after the handshake, so the capability was announced and never offered. Found while answering "what was the decision for sampling?", not by a test — the tests called the passthrough directly, which is how it passed. - Update Rust crate bimap to 0.6.3 #66 (merged) —
mcpx exec --typecheck=onaccepted the flag and ignored it, becauseopts.TypeCheckwas assigned inside theelsebranch of the inline/file split. Found by asking "does a typo'd namespace fail validation or partially run?" It partially ran. Two further faults were stacked underneath it and each hid the next. - Update Rust crate petgraph to 0.6.4 - autoclosed #74 (closed) —
--launcherand--allow-repeataccepted bymcpx execand silently dropped, becausewriteLauncherwas guarded byif opts.File != "". Filed with the observation that this was already "the third instance of the same shape", and the proposal that a test should walk the command table. - Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 (merged) — the fix. Auditing the one flag found five:
--launcher,--no-launcher,--export,--allow-repeat,--no-capture-console, all defined forexec, all in its help, all read only when a file was given. It shippedTestEveryExecFlagChangesSomething, and its closing note is the thesis of this tracker: "This fault has now shipped three times … and each was found by accident, because a flag that does nothing looks exactly like a flag that worked."
Four closed instances, six open ones, ten total, zero found by a test written to find them.
The systemic fix: guard tests
The repo has already invented the answer three times without generalising it. A guard test
takes a declared set and asserts that every member of it does something observable.
| Existing guard | file:line | Declared set it walks | How it proves the member works |
|---|---|---|---|
TestEveryDeclaredRouteAnswersOverTheSocket |
internal/e2e/v1ops_test.go:287 |
api.Ops() |
real request over the daemon socket |
TestEveryExecFlagChangesSomething |
internal/e2e/e2e_test.go:2743 |
hand table of 4 flags | runs the same source with and without the flag and requires the outcome to differ |
TestNoPackageRedeclaresADefault |
internal/defaults/guard_test.go:92 |
defaults.json |
regex source scan for constants declared elsewhere |
TestEverySettingIsReadSomewhere |
internal/settings/consumed_test.go:73 |
the settings registry (149 paths at 05c78b2) |
the path must appear as the argument to an accessor on a resolved Set, outside internal/settings |
TestNoHandRolledSettingEnv |
internal/settings/consumed_test.go:145 |
same, facing outward | no os.Getenv of a variable the registry owns |
TestThePluginReadsNoUndeclaredSetting |
internal/settings/consumed_test.go:209 |
plugin TypeScript env reads | every honoured variable resolves via sch.ByEnv |
consumed_test.go is the most developed of the four and worth reading before writing the
next one, for two reasons stated in its own header comment. First, it rejects the obvious
design — a ReadBy: "daemon" field on the declaration — because "that note sits in the same
struct literal as the declaration, gets written in the same keystroke by someone who believes
both halves, and nothing checks the second." A guard must ask the code, not the author.
Second, it narrows the question from "does this string appear anywhere" to "is it handed to
an accessor on a resolved Set", because pool.max was honoured from a config file and
ignored from its variable and its flag — half-read, which a grep calls live. It found 54
dead settings of 149 (#171).
The generalisation: for every surface that declares a set, there is a test that walks the
declaration and proves each entry does something. Where no declaration exists, the first
task is to create one — an undeclared set cannot be guarded, only audited by hand, which is
what got us here.
Surface inventory — guarded vs unguarded
Counts and line numbers checked on 8001f23.
| Surface | Declaration lives in | Guard test | Status |
|---|---|---|---|
Settings registry (149 at 05c78b2) |
internal/settings/registry.go, exec.go, proto.go, plugin.go, consumer.go |
registry_test.go:27,41,287; consumed_test.go:73,145,209; docs_test.go:19 |
guarded, both directions — the model for the rest |
/v1 routes ↔ ops table (190 op names) |
internal/api/ops*.go, Ops() at ops.go:140 |
internal/api/parity_test.go:54,73; internal/e2e/v1ops_test.go:287 |
guarded — but route discovery is a regex over internal/daemon/*.go and the e2e probe asserts only "not 404/5xx" (see #129) |
| MCP tools from the ops table | internal/api/ops*.go |
internal/api/parity_test.go:87 |
guarded (non-streaming ops only) |
| Package defaults | internal/defaults/defaults.json |
internal/defaults/guard_test.go:92 |
guarded, leaking — regex scan, and an outOfScope allowlist exempting eight whole packages that nothing shrinks |
| Plugin options ← registry | internal/settings/plugin.go |
consumed_test.go:209 |
guarded one way — the reverse (13 options in plugin/opencode/README.md vs 9 registered) is unguarded, #159 item 9 |
| exec/run flags | internal/cli/commands.go:434-481 — 23 hand-declared, plus whatever parseFlags binds from the registry |
e2e_test.go:2743 covers 4 |
effectively unguarded — the table is hand-maintained with a comment saying "add a row when you add a flag"; three flags #75 itself fixed have no row |
| All other per-command flags | ~118 hand-rolled fs.String/Bool/Int/Duration/Var calls across internal/cli/*.go |
none | unguarded — the exact shape of #74/#75 on every command except exec |
| CLI commands | Commands() at internal/cli/manual.go:33 (32 entries) vs the dispatch map at cmd/mcpx/main.go:119-161 (42 keys) |
none | unguarded — serve, openapi, adapter, registry, api, man, completion and the logs alias are dispatched and undeclared, so they are absent from mcpx help <cmd>, the man page and completion (#159 item 1) |
MCP tools from Extra (adapters, OpenAPI specs, /v1 proxies) |
internal/cli/serve.go:346,391; internal/mcpserver/server.go:106-194 |
none | unguarded — computed once at server construction, invisible to internal/codegen, so tools/list and the generated client disagree by construction (#102) |
Generated TS client ↔ tools/list |
internal/codegen vs internal/mcpserver |
none | unguarded — the parity test #102 proposes; this is the mechanical half of "every documented capability exists" |
MCPX_* inbound, unregistered |
nowhere — 44 of 64 referenced names | none possible today | unguarded, and undeclarable — TestNoHandRolledSettingEnv cannot see them precisely because the registry does not own them (#172) |
MCPX_* outbound (script contract) |
nowhere — set by internal/runner/internal/execsvc |
none | unguarded — needs a declared table before a guard can exist (#172 approach C) |
diagnose.CallError and any other built-and-uncalled export |
internal/diagnose/diagnose.go:483 |
none | unguarded — no linter runs on mcpx in CI at all, so an unreferenced export is invisible; #116 asks for a targeted test, the class asks for a dead-export check |
| Script-file resolution | two implementations: internal/cli/scripts.go:254 and internal/execsvc/execsvc.go:326 → internal/runner/runner.go:293 |
none | unguarded — one concept, two behaviours, no test comparing them (#106, class half) |
docs/parity.md (69 table lines) |
hand-written | none | unguarded — no test reads the file; the parity test checks routes/ops/tools and never the document |
OpenAPI documents (three: mcpx openapi, mcpx schema --format openapi, GET /v1/openapi.json) |
internal/api/openapi.go + hand-written |
none | unguarded — they describe different things and one describes a server that was removed (#159 items 3, 4) |
Docs code fences (374 across docs/*.md) |
the prose | none | unguarded — nothing executes them; docs_test.go:19 checks only that docs/configuration.md lists every setting |
/v1 op ↔ CLI command parity |
api.Ops() vs Commands() |
none | unguarded — #76's table exists as prose, not as a test |
Eleven unguarded surfaces, four guarded, two guarded in one direction only. Every open
instance in this tracker sits on an unguarded row.
Division of labour with #129
#129 (mcpx testing: guard tests that close a whole bug class, under #99 under #91) and this
tracker are the two halves of one subject and must not be merged or duplicated.
| This tracker | #129 | |
|---|---|---|
| Owns | the bugs — the six open instances, and the inventory of which surfaces are unguarded | the policy and mechanics — how a guard is written, when a bug counts as a class, where exemption lists live |
| Deliverable | each instance fixed; each fix names the surface it leaves unguarded | the rule (enumerate → completeness → probe → ratchet → negative test), and the guard implementations |
| Answers | which surfaces need a guard, and what promise each is currently breaking | how each guard is built, and how to keep it from decaying |
| Does not own | the shape of the test, the exemption-list ratchet, the no-network and no-leaked-process guards, the AST-vs-regex choice | the specific broken promises, or the priority order of fixing them |
Concretely: the surface inventory table above is this tracker's output and #129's input.
When #129 picks its next guard, it picks a row from that table. When a fix here closes an
instance, it reports back which row moved from unguarded to guarded. #129's mechanics
sections (approaches A/B/C for writing guards, the no-network sub-choice, the exemption
ratchet, the negative-test requirement) are not restated here and should not be.
Overlap to watch: #129's candidate list already names "every exec/run flag has a row",
"every command's flags", "every /v1 op has a CLI command", and the #102/#106 parity
tests. Those are #129's to build. This tracker's job is to make sure the list is complete
and that the underlying bug is actually fixed, not merely guarded against recurring.
Proposed order
- Bump build/buck/preludes/buck2-prelude from
d956689tocb5dc60#108 — one line,--experimental-strip-types→--experimental-transform-types, plus a test that a file containingenumruns under--runtime node. Ease S, high impact, no dependencies. Ship it first because it is the cheapest possible demonstration that the pattern pays: the fix and its guard are both smaller than this paragraph. - chore(deps): update github.com/developing-today-forks/charm digest to 15e844d - autoclosed #116 — wire
diagnose.CallErrorat the daemon's call boundary. Ease S for the wiring. Second because it is also small, and because a function that has been tested-but-uncalled for months is the clearest illustration of the class to anyone who has not seen it. - Bump build/buck/preludes/buck2-prelude from
d956689tof12c8d1#102 (doc half) — delete the false sentence fromdocs/story.mdnow. There is no reason to keep shipping a known-false claim while the capability is designed. Minutes. - Phase 2: membership, delegation, groups, and server config #172 (inbound half) — move the inbound
MCPX_*variables into the registry. Mechanical, 44 decisions, and each one converts a source-only knob into four surfaces at once. Do this before the generated reference (Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96/🎨 Palette: Improve accessibility of editor search panel #144/⚡ Bolt: Optimize cursor color generation and position grouping #145) is built, or the reference is generated from an incomplete source. - Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 (class half only) — one script-resolution rule for CLI and daemon. Larger, and it unblocks the harness-free plugin (Update Rust crate anyhow to 1.0.75 #48, Bump rustls-webpki from 0.100.1 to 0.100.2 in /sources/pavex/examples/realworld #44), which has a socket and no binary and therefore cannot resolve a name locally.
- Bump build/buck/preludes/buck2-prelude from
d956689tof12c8d1#102 (capability half) — extras as a real tool source, via chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to36464cf#83, with thetools/list↔ generated-client parity test. - Phase 2: membership, delegation, groups, and server config #172 (outbound half) — declare the script environment contract, then guard it.
- Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 — under Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96, on its own schedule; sequence it so the doc fixes land before anything is generated from those sources.
Added in the 2026-09-30 pass, slotted by cost rather than appended:
- Beside step 1, because each is smaller than its description and none has a dependency:
Phase 3: test_an_interrupted_session_restarts_without_duplicates flakes under full-suite load #188 (sort the header keys — three lines and a determinism test), Phase 4: groups, encryption, and history #191 (pass the resolved
key into the log line — one parameter), Phase 5: persisted incremental tag index #183 (correct one comment and assert the two
properties it should have described), id: lib does not build with --no-default-features #193 (the events replay gap marker; Phase 4 fix: a joiner fetches the epoch it missed #192 is the same
— Update Rust crate petgraph to 0.6.4 - autoclosed #74's class on the remote path). - Beside step 2, because it is the same "hand-written client disagrees with the declaration"
shape and the first half is an hour: Phase 4: joiner misses an update made without knowing its concurrent add #189's three field renames. Its generated-types half
belongs with Update Rust crate clap to 4.5.4 - autoclosed #76's approach C, not here. - Before the release work in amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158: Phase 3: range read_flight buffers frames with no cap while more is set #178. A release pipeline needs a reproducible derivation,
one version source, and generated artefacts it can trust; none of the three exists yet. - Phase 3: sync data phase batches are bounded by count, not bytes #180 first of all of them, as the super tracker says: it is editing rather than engineering,
and one of its corrections stops a wrong fix (MCPX_SESSION) and another removes a blocker that
was never real (the static-binary claim). - Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179 feeds Add
omasnapupstream repository to flake inputs #129 rather than waiting on it. The two defects are small; the finding — that
consumed_test.goproves a setting is read, not that reading it changes behaviour — is the
input Addomasnapupstream repository to flake inputs #129 needs, and it must be applied before or with Phase 2: membership, delegation, groups, and server config #172's 44 new declarations. - Serve stalls before printing node in test_world_module_shared_over_iroh_and_web_presentations #190 is the one that needs a decision before code. Case-folding is the obvious answer and the
wrong one; it trades over-isolation for accidental sharing. Read its approach C first.
Throughout: each fix names the surface row it sat on and hands it to #129. A fix that closes
an instance without identifying its row has not finished.
Ease / impact
Pulled verbatim from each instance's own footer.
| Issue | Ease | Impact | Note |
|---|---|---|---|
| #102 | S (doc fix + test) / M (if the capability lands here rather than #83) | med | "the documented feature is a headline example in docs/story.md" |
| #106 | M (dispatch and cwd policy) / L (shared script resolution across CLI and daemon) | high | "it turns exec from 'run TypeScript' into 'run a script', and it closes a real CLI-vs-/v1 divergence of the kind #76 tracks" — only the resolution half is this tracker's |
| #108 | S for the flag (one line + a test asserting a non-erasable file runs under --runtime node), M for the probe and messaging |
high | "silently restricts the language on any machine without deno or bun" |
| #116 | S for the wiring, M with the structured field decided | med-high | "turns an opaque protocol error into the specific reason, on every surface at once" |
| #172 | M (mechanical, but 44 call sites and a decision per variable) | med-high | "every inbound one is a user-facing option missing from four surfaces at once" |
| #159 | M (items are small; C's single-sourcing is the bulk) | high | "blocks an accurate reference" — native sub-issue of #96 |
| #178 | S (a handful of lines each; the presence test is the bulk) | med | "nothing is broken for a user today; everything here is a prerequisite that gets more expensive the longer #158 waits on it" |
| #179 | S | low–med | two small defects, and the guard-test hole that is this tracker's most important finding |
| #180 | S | med | editing, not engineering — and one of its corrections stops a wrong fix being made |
| #183 | S | low–med | "no user-visible bug, but it misdescribes the mechanism a reader most needs to trust when debugging which daemon answered" |
| #188 | S | med | duplicated pools and unstable keys, intermittently — the expensive kind of wrongness |
| #189 | S (the three fixes) / M (round-trip test) / M–L (generated types) | high | the documented way to call a tool from the plugin passes neither the arguments nor the session |
| #190 | M (the fix is small; the decision and cross-platform verification are the work) | med | one extra server process per wrong-case spelling, silently, with no wrong answers |
| #191 | S | low–med | a diagnostic that reports the opposite of what happened, at the moment someone is debugging isolation |
| #192 | S (B) / M (C) | med-high | "remote exec is the path the ssh and browser surfaces (#174) build on" |
| #193 | S (A, B) / M (C, with #6's gap contract) | med | silent loss on the one path documented as lossless |
Warnings
-
A guard that cannot be enumerated is a reminder, not a guard.
TestEveryExecFlagChangesSomethingis the cautionary example in this repo: it was written as the fix for Update Rust crate petgraph to 0.6.4 - autoclosed #74/Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 and it has four rows against 23 hand-declared flags plus every exec-scoped setting the registry binds, including three flags Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 itself repaired. Its comment says "Add a row when you add a flag" — which is the memory the guard was created to replace. Any new guard that starts from a hand table inherits this failure on day one. -
Acceptance is not effect, and Bump build/buck/preludes/buck2-prelude from
d956689tocb5dc60#108 is the proof. Node 24 accepts--experimental-strip-typesand exits 0. A guard asserting "every flag we pass to a subprocess is a flag that subprocess accepts" passes cleanly on the exact bug in this tracker. Guards must observe a difference in outcome (the Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 with-and-without pattern), never the absence of an error. -
Fixing all six without adding guards is the failure mode this tracker exists to prevent. Bump checkmarx/ast-github-action from 2.0.19 to 2.0.20 #39, Update Rust crate bimap to 0.6.3 #66, Update Rust crate petgraph to 0.6.4 - autoclosed #74 and Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 were each fixed individually and correctly, and the class arrived four more times. If the fix PRs land and the inventory table above is still eleven rows of "unguarded", nothing has been learned — record that plainly in the closing comment rather than closing quietly.
-
A guard with an allowlist decays into a no-op unless something shrinks it.
TestNoPackageRedeclaresADefaultexempts eight whole packages viaoutOfScope"owned by work happening in parallel", and that parallel work has since landed.consumed_test.go'sunreadAllowedhas exactly one entry with a paragraph of justification — that is the standard to copy, and Addomasnapupstream repository to flake inputs #129 owns the ratchet that enforces it. -
Regex source scans pass silently when they are wrong. PR chore(deps): bump rustix from 0.37.19 to 0.37.25 in /sources/pavex/examples/realworld #56: the defaults guard matched
"5 * time.Minute"while gofmt writes5*time.Minutein call arguments, so nine live constants were invisible and the test reported green. Two of the six guards in the table above are regex scans. Prefer walking a registry; usego/astwhen there is no registry; never regex when a declaration exists to enumerate. -
Do not run the full test suite to check any of this.
go test ./...is ~135 s withinternal/e2eat ~101 s. The four-hour daemon leak is fixed — Phase 5: tag event bodies must travel with the event #182 closed chore(deps): bump build/buck/preludes/buck2-prelude fromd956689toe7457f6#52 by having cleanup callstop --allthrough thedaemon-<key>.jsoninfo files and settingMCPX_AUTOSTART_IDLE_EXIT=60sin every test environment (internal/e2e/e2e_test.go:68-83) — so the reason is now time, not hygiene. What is still true is Update dependency htmx.org to v1.9.12 #138: the suite inherits the caller's environment, so a run can reach a developer's real daemon. Run the specific package. -
Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 and Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 must not be re-parented. Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 is a native sub-issue of Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96, Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 of fix(id): make Nix E2E check reproducible #175, and GitHub permits one parent. Adopting either here would silently remove it from the tracker where the work is actually scheduled. Native sub-issues of this tracker at
05c78b2are Bump build/buck/preludes/buck2-prelude fromd956689tof12c8d1#102, Bump build/buck/preludes/buck2-prelude fromd956689tocb5dc60#108, chore(deps): update github.com/developing-today-forks/charm digest to 15e844d - autoclosed #116, Phase 2: membership, delegation, groups, and server config #172, Phase 3: range read_flight buffers frames with no cap while more is set #178, Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179, Phase 3: sync data phase batches are bounded by count, not bytes #180, Phase 5: persisted incremental tag index #183, Phase 3: test_an_interrupted_session_restarts_without_duplicates flakes under full-suite load #188, Phase 4: joiner misses an update made without knowing its concurrent add #189, Serve stalls before printing node in test_world_module_shared_over_iroh_and_web_presentations #190, Phase 4: groups, encryption, and history #191, Phase 4 fix: a joiner fetches the epoch it missed #192 and id: lib does not build with --no-default-features #193; Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 and Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 are members of the class listed in the task list above and nothing more.
Related
Parent: #181
Related: #129 (the testing-side half — guard mechanics and policy; read it before writing any
guard), #99 (its tracker), #91 (the strategy above both), #49 (CI for every package — a guard
that does not run in CI is a guard nobody runs), #52 (closed, fixed in #182 — the leak is gone;
#138 still owns the environment-inheritance half), #159 (member of this class, tracked natively under #96), #175 (runtimes tracker, which owns #106), #171 (the merged settings
audit that produced consumed_test.go and found 54 dead settings of 149), #83 (one tool-source
interface, which #102's capability half belongs to), #76 (CLI ↔ /v1 parity, an unguarded row),
#96 / #144 / #145 (the generated reference, which reads several of these declarations and will
publish whatever they contain).
Closed precedent: #39, #66, #74, #75.
Security/auth deferred; see tracker.
Activity
developing-today commentedon Oct 28, 2026
CODE-820
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#177 (verbatim, oldest first):
@dezren39 · 2026-09-30T12:25:19Z
Issues pass (2026-09-30, at
05c78b2). Brought current; the class argument and the surfaceinventory are unchanged, because both are still right.
Parent: (super tracker, to be linked)resolved toParent: #181, in both places.(the derivation's generated man page, completions and version), Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179 (
registry.pageSizereadbut unreachable), Phase 3: sync data phase batches are bounded by count, not bytes #180 (the re-verification
reviewartefact), Phase 5: persisted incremental tag index #183 (ForConfig's comment),Phase 3: test_an_interrupted_session_restarts_without_duplicates flakes under full-suite load #188 (
PoolIDhashes headers in map order), Phase 4: joiner misses an update made without knowing its concurrent add #189 (the plugin sends field names the daemon doesnot read), Serve stalls before printing node in test_world_module_shared_over_iroh_and_web_presentations #190 (wrong-case cwd keys separately), Phase 4: groups, encryption, and history #191 (the degraded-scope log line reports the
opposite of what happened), Phase 4 fix: a joiner fetches the epoch it missed #192 (a remote exec drops most exec flags — Update Rust crate petgraph to 0.6.4 - autoclosed #74's class on the path
Update Rust crate proc-macro2 to 1.0.81 - autoclosed #75 did not cover), id: lib does not build with --no-default-features #193 (
/v1/eventsreplay drops past 256 without a gap marker). Each has arow in the ease/impact table and a place in the order.
guards, it passed the newest one.
consumed_test.goproves a setting is read, not thatreading it can change behaviour. That is a hole in this tracker's own model of "guarded", and
it is why Add
omasnapupstream repository to flake inputs #129 must be applied before or with Phase 2: membership, delegation, groups, and server config #172's 44 new declarations.docs/opencode-plugin.md"namesMCPX_SESSIONwhere the code sets
MCPX_SESSION_ID". That framing is wrong and acting on it would break everygenerated client:
MCPX_SESSIONis mcpx's own outbound variable, read by the generated clientat
internal/codegen/emit.go:253. What is wrong is the doc line itself(
docs/opencode-plugin.md:406), which describes what the plugin sets —MCPX_SESSION_IDandMCPX_CALL_ID(plugin/opencode/mcpx-session.ts:670,677).MCPX_CALLexists nowhere. This isPhase 3: sync, blob gate, and relays #181 collision 16.
d956689toe7457f6#52 is closed: Phase 5: tag event bodies must travel with the event #182 made cleanup callstop --allthrough the info filesand set
MCPX_AUTOSTART_IDLE_EXIT=60s(internal/e2e/e2e_test.go:68-83). The reason not to runthe whole suite is now time, not hygiene — and Update dependency htmx.org to v1.9.12 #138 is still open: the suite inherits the
caller's environment and can reach a developer's real daemon.
Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 and Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 remain members of the class, not children — they are natively parented to fix(id): make Nix E2E check reproducible #175 and
Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96 and re-parenting either would remove it from where the work is scheduled.
internal/settings/*.goat05c78b2.Noted that Phase 2: membership, delegation, groups, and server config #172's 44-of-64 and Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159's "about 49" have never been reconciled — Phase 3: sync, blob gate, and relays #181 collision
17, owned by Phase 2: membership, delegation, groups, and server config #172, whose first deliverable is the allowlist itself.
@dezren39 · 2026-09-30T12:26:14Z
Issues pass (2026-09-30, at
05c78b2) -- for whoever lands this. This issue is in flight, so its body was not edited. These came out of re-checking every other issue against currentmain; each is verified at05c78b2unless marked./v1/eventsreplay loses events past 256 with no gap marker. Both were found by reading the code and have not been reproduced.paths.configis dead on all three channels: config key,MCPX_PATHS_CONFIGand--paths-config.docs/configuration.md:336advertises two of them, and the guard's allowlist excuses it with a false reason. Owned by Bump build/buck/preludes/buck2-prelude from3a02ea3to370cd4d#5; listed in Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 item 19 and Addomasnapupstream repository to flake inputs #129 leak 7.defaults.jsonholds that the registry does not:elicit.ttl/pollInterval/handlerTimeout/inputRoundsandexec.workDirs/programs/entries.docs/protocol.md:255callselicit.ttla setting (see Phase 2: membership, delegation, groups, and server config #172, Phase 4: joiner with missing pre-join control messages stalls silently #187).samplingto every upstream whenever an elicitation handler is installed, which is always (internal/mcpclient/modern.go:54-56).docs/protocol.md§2.2 still listsloggingas declared (Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 item 18, fix(deps): update rust crate leptos to v0.8.22 #77).@dezren39 · 2026-09-30T12:28:08Z
Follow-up: the task-list bullet in my previous comment was overtaken by the 12:25 revision a minute earlier -- #183 and #188-#193 are now in the body. The other bullets stand.
@dezren39 · 2026-09-30T12:32:58Z
Correction to the pass comment above: the settings registry declares 149 paths at
05c78b2, not 151. 104 acrossregistry.go/exec.go/proto.go/plugin.go/consumer.goplus 45 more inwire.go(theautostart.*family and others), with no overlap; a naive grep overinternal/settings/*.goreaches 151 only by counting two test-only paths inregistry_test.gotwice over. The body is corrected. #181's evidence section had it right.@dezren39 · 2026-09-30T19:35:44Z
SURFACES pass (2026-09-30). Instances of this class found and fixed while shipping the SURFACES list, each with a test that fails without the fix. Rows of the surface inventory that moved from unguarded to guarded are marked → guarded.
Commands()declared 34 names, main dispatched 42 (serve openapi adapter registry api man completion+logs)internal/cli/manual.govscmd/mcpx/main.goTestEveryDispatchedCommandIsDeclared→ guarded (CLI commands row)TestEveryOperationIsReachableFromTheCLI,TestEveryGeneratedCommandReachesItsRoute→ guarded (/v1↔ CLI row)docs/parity.md33 rows / 60 opsTestParityDocumentIsCurrent→ guardedDaemonClient.call()sentarguments/sessionId;/v1/callreadsargs/session-- every tool called with no argumentsplugin/opencode/mcpx/daemon.tsgo test(TestThePluginSuiteRunsAgainstARealDaemon)mcpx_artifact_putstored{}as every artifact's contentParam.Raw)TestMCPArtifactPutStoresTheContentmcpx initwrote"mode", which the loader dropsstarterConfigTestStarterConfigMeansWhatItSays; unknown keys in themcpxblock are now refused by nameelicit.confirmDestructivefailed open on a cold schema cache -- the destructive call ran unaskedinternal/daemon/consumer.goTestConfirmDestructiveHoldsOnAColdDaemon/v1/diagnoseused an empty catalog on a cold daemonTestARecipeRunOnAColdDaemonSeesItsServers,TestDiagnoseOverV1OnAColdDaemonSeesItsServers/v1served cross-origin requests on loopback TCP (a web page couldPOST /v1/exec) while/mcprefused theminternal/daemon/server.goTestAWebPageCannotRunCodeThroughTheDaemon,TestV1AndMCPAgreeOnOriginsprompt.mode(and six other call-scoped consumer settings) ignored from env, flag andPUT /v1/settings, which answeredapplied: true; an unknown value silently meant the defaultinternal/daemon/consumer.goprompt.autonomy, decision 0002)TestTheCallersPromptAutonomyReachesTheDaemon,TestAPromptAutonomySetOverTheAPIIsHonoured,TestAnUnknownAutonomyIsRefusedmcpx exec --before/--on-success/--on-erroraccepted and droppedinternal/cli/commands.goTestASnippetRunsItsBeforeAndOnSuccessPhasesonErrorandsuffixTestAThrowingSnippetRunsOnError,TestAFileThatThrowsAtTopLevelRunsOnErrorAndSuffixmcpx exec --remotelost its outputinternal/cli/exec.goTestARemoteExecPrintsItsOutputcall.started,task.updateddeclared, never publishedinternal/eventsscript.permissions strictran with full authority on node/buninternal/runnerTestAStrictScriptCannotReadFilesOnARuntimeWithoutPermissions+ unitrestartclaimed MCPidempotentHintTestIdempotentHintIsTrueapi.Boundaries()TestEveryEnforcedBoundaryNamesItsRefusal→ guarded (declared-vs-enforced row)Merged earlier from the same list: #230 (#116,
diagnose.CallErrorwired), #237 (#172, MCPX_* declared, script env contract; acronyms in derived names), #243 (#179), #227 (#183), #248 (openapi, #102 guard).Still unguarded from the inventory: per-command hand-written flags other than
exec(~118), script-file resolution local vs remote (#106), docs code fences. Flakes seen while verifying, filed: #270 (TestAStreamConsumerCanCancelAfterTheResult), and already-filed #238, #272.dezren39 commentedon Oct 29, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 29, 2026
Triage verdict — task 2801 (lane D)
Verdict: NOT a duplicate. Scope confirmed against current
pkgs/mcpxmain. Every instance in the body is now accounted for: nine were fixed in the nix repo before relocation (never moved — nothing is owed for them), six were relocated and are open here with their own queued triages, and one (the re-verification artefact) was closed today. The guard half — the part this tracker says matters — is partially landed on main, and its remainder is already owned by queued testing issues (#1971, #1909, #1969). No new packet is enqueued for #1893 itself; that would duplicate those owners.Instance mapping (nix numbers in the body are historical)
Fixed before relocation — closed in dezren39/nix with the fix PR named on the original; do not re-file:
--runtime nodestrip-types flagdiagnose.CallErrornever calledRegistry.Call, all six surfaces)MCPX_*variablesScriptEnv()contract for every one; shrink-only allowlist; generateddocs/environment.mdwith drift test)registry.pageSizeunreachable /nextCursordroppedForConfigcomment describes removed keyingResolved.PoolIDhashesHeadersin map orderRelocated — open here, each with its own queued triage (per the 2026-10-28 placement-audit move lists):
/v1/eventsreplay drops past 256 silentlyRelocated and now closed: #180 → #1897, triaged and closed today (task 2791, lane C): most findings fixed/moot at current main, live remainders owned by #1883/#1895.
Per the body's own warning 7, #159 and #106 are members of this class only — they stay parented/tracked under #1903's tree and #1889 respectively; nothing here re-parents them.
Scope confirmation vs current main — the guard half
Checked at
main=8124cc0b7005(2026-10-29T05:56:32Z):TestEvery*tests acrossinternal/cli(dispatch_test.go,parity_test.go,flagreach_test.go— includingTestEveryDispatchedCommandIsDeclared),internal/api(parity_test.go,boundaries_test.go),internal/settings(consumed_test.go,registry_test.go),internal/codegen/catalog_test.go, andinternal/e2e(toolparity_test.go,v1ops_test.go,settings_test.go).TestEveryExecFlagChangesSomethingexists (internal/e2e/e2e_test.go) in the hand-rowed form the body criticises — growing it into an enumerated guard is precisely the remaining work.queue/queued: mcpx testing: guard tests that close a whole bug class #1971 (guard tests that close a whole bug class — this tracker's direct implementation follow-up), mcpx testing: policies for agents -- what to run, a test per bug, guards per bug class #1909 (agent policies: a test per bug, guards per bug class — the body's Addomasnapupstream repository to flake inputs #129 half), mcpx testing: a failing-first test for every bug, and a review of recent fixes #1969 (a failing-first test for every bug, and a review of recent fixes).Verification commands for the follow-ups (per the triage packet)
go test ./internal/cli/ ./internal/settings/ ./internal/api/ ...viamcpx.yml) — the body warns the full suite is ~135s and inherits the caller's environment (mcpx testing: e2e inherits the caller's environment -- build it from an allowlist so tests never reach the user's daemon #1992 owns that).Outcome
#1893 stays open as the class tracker. This triage task completes; follow-ups are the instances' and guard issues' existing queue tasks named above.
🤖 Agent note · done
14 remaining items
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass)
Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 30 (priority/none). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
Found via: coverage pass 2026-11-03
dezren39 commentedon Nov 4, 2026
agent-note:v1
event: picked_up
task: 8716 (triage, repo-health-code-pilot; repo-worker-code; OpenChamber scheduled lane aea12c82-c5af-4e5a-9b66-f8e360f87177)
state: claimed; fresh GitHub state: #1893 is OPEN with no hard-stop labels. The tracker remains live: guard remainder #1971 is OPEN and already routed through task 8158 (claimed, execution lane opencode); related tracker #1909 remains OPEN; #1969 is CLOSED after merged PR #5055. Prior triage found this is not a duplicate and that no new #1893-specific implementation packet should duplicate the owned children.
next: post triage verdict with fresh evidence and complete task 8716 as triaged, referencing existing follow-up task 8158.
dezren39 commentedon Nov 4, 2026
Triage verdict — task 8716
Verdict: still live; keep #1893 open. The tracker is not a duplicate. Its remaining purpose is the class-wide guard coverage; do not enqueue another implementation packet that duplicates the existing child owners.
Fresh state (2026-11-04)
The previous instance-by-instance triage (task 2801, re-confirmed by task 7772) remains consistent with this state: the guard remainder stays with #1971 and the separately tracked children; #1893 remains a coordination tracker until that work lands. No code or PR changes were made in this triage.
Route: continue task 8158 for #1971; keep #1893 open and do not create a duplicate follow-up.
Evidence: #1971 #1971 · #1909 #1909 · #1969 #1969 · PR #5055 #5055
dezren39 commentedon Nov 4, 2026
agent-note:v1
event: done
task: 8716 (triage, repo-health-code-pilot; repo-worker-code; OpenChamber scheduled lane aea12c82-c5af-4e5a-9b66-f8e360f87177)
state: triaged; queue task completed. Issue #1893 remains OPEN and is not a duplicate. Existing guard remainder task 8158 owns #1971; no duplicate follow-up task was created. Fresh route evidence and the verdict are in the triage comment above.
labels: queue/claimed, status/in-progress, and work/assigned removed; non-flight labels retained.
links: task 8158 / #1971 · merged PR #5055