Skip to content

mcpx: paths.config governs the config chain (two-pass, #1842) - #2477

Merged
dezren39 merged 2 commits into
mainfrom
queue/3059-1842-paths-config-chain
Oct 30, 2026
Merged

dezren39 merged 2 commits into
mainfrom
queue/3059-1842-paths-config-chain

Conversation

@dezren39

@dezren39 dezren39 commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Implements issue #1842 (Approach C, settled by triage task 2736): paths.config now governs the rest of the config chain.

What changed

  • New config.ResolveChainFrom (pkgs/mcpx/internal/config/chain.go) — two-pass resolution:
    • Discovery pass: the default locations (SearchPathFrom), seeded by MCPX_PATHS_CONFIG / --paths-config, are read for paths.config only, honouring the null splice.
    • Load pass: that list is resolved and loaded, nearest last. A file's paths.config governs the chain farther than itself; a list with no null replaces the farther remainder.
    • A cycle is refused by identity, naming the path that closed it.
    • A paths.config found only in pass 2 is reported, not applied (ChainInfo.Divergent, shown by mcpx config --sources).
    • MCPX_CONFIG bypasses both passes.
  • LoadFrom and the settings file layer (configFilesFarthestFirst) both consume the resolved chain, so added files contribute settings as well as servers.
  • --paths-config is a global flag captured in main alongside --config (it decides which files settings are read from, so it must be captured before any settings exist).
  • mcpx config --sources shows the discovery pass and load pass distinctly, plus any divergence reports.
  • Truth-ups in the same PR: the consumed_test.go allowlist comment for paths.config, docs/declared-but-unread.md (no longer "left open"), and docs/configuration.md (two-pass section). warnPathsConfigFile now warns on entries naming nothing that exists — file entries mean exactly that file now, so the old mcpx daemons: dead records listed forever, custom-state daemons missed, paths.config file entry silently ignored #278 warning no longer applies.

Tests

  • 6 new tests in internal/config/chain_test.go: pass-1 extension changes the load set, cycle refused naming the closing path, pass-2 divergence reported and not applied, MCPX_CONFIG bypass, env seeding, no-null replacement.
  • Verified locally (laptop): go test ./internal/config/ ./internal/settings/ pass; internal/cli passes isolated; go vet clean.
  • Note: a full go test ./... run under parallel-suite contention showed one internal/daemon termination ("signal: terminated", 11s) that did not reproduce in isolated package runs; CI is the arbiter for the full suite.

Queue task 3059. Merging remains gated by the merge-queue issue #2403 — do not merge until that clears; a follow-up lane will verify CI and complete the task.

agent-note:v1 — worker lane D, task 3059.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Approach C from issue #1842: config.ResolveChainFrom resolves the chain in two passes. Discovery walks the default locations seeded by MCPX_PATHS_CONFIG / --paths-config (global flag captured in main like --config) and reads only paths.config, honouring the null splice; the load pass resolves that list, each file governing the chain farther than itself. A cycle is refused by identity naming the closing path; a paths.config found only in pass 2 is reported via ChainInfo.Divergent and mcpx config --sources (which now shows both passes distinctly), never applied. MCPX_CONFIG bypasses both passes. LoadFrom and the settings file layer both consume the resolved chain. consumed_test allowlist comment and docs (declared-but-unread.md, configuration.md) truthed up. warnPathsConfigFile now warns on entries naming nothing that exists, since file entries mean exactly that file now.
@dezren39
dezren39 requested a review from a team as a code owner October 30, 2026 00:28
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 55 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fbb13c9a-3f6a-47f9-abfb-913711510ba7
Commits

Reviewing files that changed from the base of the PR and between 29a4331 and e013229.

Files selected for processing: 9
  • pkgs/mcpx/cmd/mcpx/main.go
  • pkgs/mcpx/docs/configuration.md
  • pkgs/mcpx/docs/declared-but-unread.md
  • pkgs/mcpx/internal/cli/root.go
  • pkgs/mcpx/internal/cli/settings.go
  • pkgs/mcpx/internal/config/chain.go
  • pkgs/mcpx/internal/config/chain_test.go
  • pkgs/mcpx/internal/config/config.go
  • pkgs/mcpx/internal/settings/consumed_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T00:33:54.241773Z bcf9151 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

TestNoUndeclaredEnvRead caught chain.go reading MCPX_PATHS_CONFIG by name: it seeds the discovery pass before any Set exists, so it belongs on rawReadAllowed with the reason, alongside MCPX_CONFIG. Verified with -count=1 (the guard reads the tree at runtime, so a cached test result can hide this).
@dezren39

Copy link
Copy Markdown
Member Author

Follow-up commit e013229: the full-suite run caught a real failure the package runs missed — TestNoUndeclaredEnvRead (internal/settings) requires any pre-Set env read to be declared. MCPX_PATHS_CONFIG is now on rawReadAllowed with the bootstrap reason, alongside MCPX_CONFIG. Re-verified with -count=1: internal/settings and internal/config pass. (The guard reads the tree at runtime, so Go's test cache had served a stale ok for settings in the earlier package run.)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcf9151591

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// A bootstrap flag like --config: it decides which files
// the settings are read from, so it is captured here,
// before any settings exist (issue #1842).
config.SetPathsConfigSeed(splitList(args[1]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward the bootstrap seed to auto-started daemons

When --paths-config adds a configuration file and a command needs to auto-start a daemon, the seed exists only in this process's flagSeed. Connect keys the expected socket from the expanded configuration, but Client.EnsureDaemon (internal/cli/client.go) forwards only --config, not this seed. Without an equivalent MCPX_PATHS_CONFIG environment variable, the child loads the default chain and listens on a different configuration fingerprint, so the client times out waiting for its socket. Propagate the bootstrap seed to the spawned process as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): config.PathsConfigSeedEnv() serializes the flag seed as MCPX_PATHS_CONFIG, and the daemon autostart path in internal/cli/client.go now sets it as the child daemon's environment, so an auto-started daemon resolves the same chain as the invoking process. Covered by TestPathsConfigSeedEnvForwardsFlagSeed (internal/config).

Comment on lines +304 to +308
for _, loc := range expandAll(info.Seeded, wd) {
add0(loc)
}
for _, loc := range SearchPathFrom(wd) {
add0(loc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the seed list's null splice around default locations

For MCPX_PATHS_CONFIG='[null,"/custom/config.json"]', expandAll discards the splice position and this code places the custom file before every default location, giving it higher rather than lower precedence. A seed without null also retains all default locations instead of replacing them. This contradicts the setting's help in internal/settings/registry.go: “Without a null the list replaces outright.” Build the discovery list using the before/after/null result so the seed does not load unintended configurations or reverse precedence.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): pass 1 of ResolveChainFrom now splices the seed around the default walk via expandEntries — entries before the null land nearer, entries after it farther, and a seed with no null replaces the default walk outright, as the registry help states. Covered by TestPathsConfigSeedNullSplicesAroundDefaults and TestPathsConfigSeedWithoutNullReplacesDefaults.

// A bootstrap flag like --config: it decides which files
// the settings are read from, so it is captured here,
// before any settings exist (issue #1842).
config.SetPathsConfigSeed(splitList(args[1]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accumulate repeated config-search flags

For mcpx --paths-config /a --paths-config /b config, each call replaces flagSeed, so only /b participates in discovery. The setting is declared Repeatable: true in internal/settings/registry.go, and the existing binding implementation accumulates repeated occurrences. The new bootstrap interception bypasses that behavior and silently drops earlier configuration sources. Collect all occurrences before installing the seed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): main now calls the new config.AddPathsConfigSeed per --paths-config occurrence, accumulating like the Repeatable setting's normal binding; SetPathsConfigSeed remains the replace/reset form. Covered by TestPathsConfigFlagSeedAccumulates.

Comment on lines +181 to +185
var doc struct {
Paths struct {
Config []any `json:"config"`
} `json:"paths"`
Flat []any `json:"paths.config"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept existing string-valued path lists during discovery

An existing configuration such as {"paths":{"config":"./shared"}} now fails discovery with a JSON unmarshal error because these fields require arrays. The normal settings file loader accepts this representation: ApplyFile preserves string values and Set.List parses comma/newline-separated strings as well as JSON arrays. Since chain resolution now runs before command dispatch, a previously accepted configuration prevents commands from running at all. Normalize the discovery value using the same accepted representations as the settings layer.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): readPathsConfig accepts a string-valued paths.config, splitting the comma/newline spelling the settings layer (ApplyFile / Set.List) accepts, in addition to arrays. Covered by TestPathsConfigStringValuedListAccepted.

Comment on lines +339 to +340
if inChain0[chainIdentity(loc)] {
edges[f] = append(edges[f], loc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Canonicalize cycle-graph endpoints by file identity

When discovered files A and B refer to each other through symlink aliases, the membership test recognizes their identities but the graph stores the original path strings. findCycle consequently treats those aliases as unrelated terminal vertices and accepts the cycle instead of returning the promised cycle error. This is reproducible with A declaring a symlink to B and B declaring a symlink to A. Use canonical identities consistently for graph keys, edge targets, and traversal roots, retaining display paths separately for diagnostics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): the cycle graph's endpoints are now canonicalized with chainIdentity, matching the membership test, so symlink aliases are the same vertex and a cycle through an alias is refused. Covered by TestPathsConfigCycleThroughSymlinkAliasRefused.

}
return -1
}
for _, f := range chain0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply declarations in the resolved chain order

When a nearer file reorders already-discovered configurations, this loop still applies their declarations in the original discovery order. For discovery order A,B,C, let A declare [C,B], B declare [X], and C declare [B], all without null. A moves C before B, but B is processed first and adds X; C then replaces its remainder with B, dropping X even though B remains in the chain. The result is A,C,B rather than A,C,B,X, silently excluding X's servers and settings. Apply declarations in the current load-chain order so a retained file's declaration is not undone by a now-nearer file processed later.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed forward in PR #5165 (issue #2507, commit 2a88cd5): declarations now apply in the resolved chain's order — the build loop rescans the evolving load chain from the nearest end after each application — so a file a nearer list moved up governs before the ones it displaced, and extensions it added are no longer dropped. Covered by TestPathsConfigDeclarationsApplyInChainOrder, which reproduces this finding's A/B/C/X example exactly.

@dezren39
dezren39 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 0b06b27 Oct 30, 2026
8 checks passed
@dezren39
dezren39 deleted the queue/3059-1842-paths-config-chain branch October 30, 2026 01:09
dezren39 added a commit that referenced this pull request Nov 3, 2026
…config-findings

mcpx: disposition the six paths.config chain findings from PR #2477 (#2507)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant