fix(mcp): resolve conventional autoload user scope from the agent directory (#4767) - #4768
Conversation
…ectory
Every MCP writer and the disabledServers denylist resolve user scope
through getMCPConfigPath("user") -- the agent directory -- while native
discovery derived it from the load context's home. The two disagreed
under an agent-directory profile: `gjc mcp add` wrote <profile>/mcp.json
and reported the server as loaded at startup, but startup read
~/.gjc/agent/mcp.json, so the profile's registrations never loaded and
the default profile's servers loaded into the profile instead.
This is also why the MCP autoload suites broke after the trusted-home
provenance rework: they isolate with setAgentDir(), and their
os.homedir() mock no longer reaches discovery -- on a developer machine
the red-team suite read the real ~/.gjc/agent/mcp.json, in CI it found
nothing.
Lore-id: 4f7a1c22
Constraint: the trusted-home snapshot stays mock-proof -- no test seam
may reopen a project-controlled home for discovery
Constraint: user-scope reads and writes must name one file
Rejected: injectable home in loadCapability | reopens the provenance
hole d9fabc8 closed
Rejected: re-express the suites against the real trusted home | leaves
the profile defect shipped and lets a red-team suite read real user
MCP configuration
Confidence: high
Scope-risk: contained
Reversibility: easy
Directive: home-relative surfaces (skills) keep resolving from
LoadContext.home; only agent-directory writers follow userAgentDir
Tested: profile agent dir discovery/writer parity, explicit agentDir
option incl. denylist, 846 MCP/discovery tests
Not-tested: Windows agent-directory profiles
Co-Authored-By: Claude <noreply@anthropic.com>
|
PR #4768 is the upstream MCP user-scope DirResolver fix required to clear PR #4702's known baseline trio. Technical checks and affected validation are green; the only contract failure is intentional because the verdict remains Please review exact head — |
|
Thanks @probepark — independent convergence on the mechanism, and the fix you describe as "the fix that should work" is what this PR already ships:
That is Two corrections, both measured. 1. The "second, separate defect" does not exist
Those three are the same defect, not a second one. They survive your patch because it points discovery at Same three as CI. Pointing the fixture at the scope the suite itself establishes with Per-file tolerance is intact in both directions on this head — direct probe, profile agent dir, capability cache dropped between measurements: {
"malformedProjectFile": ["userSrv"], // malformed project + valid user
"validProjectFile": ["projectSrv","userSrv"],
"malformedUserFile": ["projectSrv"] // malformed user + valid project
}A broken project 2. The other four sites are deliberately out of scope, and filed
Agreed that all six ignore the override; I moved only the two with a proven read/write mismatch. The others are not uniform, and moving them mechanically would create a new orphan rather than close one: {
"migrateWritesUserSkillsTo": "/tmp/<profile>/skills", // cli/migrate-cli.ts:56
"skillManagementWritesUserSkillsTo": "/Users/<me>/.gjc/agent/skills", // skill-management.ts:154
"writersAgree": false,
"runtimeDiscoveryFindsProfileSkill": false
}The two user-scope skill writers already disagree with each other, and That is a product decision, not a mechanical one, so it is #4769 with the measurements rather than bolted onto a fix that is otherwise green and unblocking #4702. MCP is not a precedent for either branch of that decision: it went to the agent directory because every MCP writer already did ( Verdict stays |
probepark
left a comment
There was a problem hiding this comment.
First review at exact head edd6f3fa — approved. This fixes the half of #4767 it claims to fix, and fixes it at the right layer.
Disclosure: I filed #4767, so I checked this against my own diagnosis rather than assuming agreement.
the mechanism is the one that was needed
capability/index.ts:239-242 adds LoadContext.userAgentDir, threaded through runtime-mcp/config.ts:147-163 and sdk/session.ts:2314-2321. That resolves the override at the context builder, so the provider stays a function of its context.
This matters because I tried the other approach — pointing builtin.ts directly at getAgentDir() — and it broke three passing tests in test/discovery/builtin-rules-md.test.ts (143/0 → 140/3). That suite deliberately injects a temp ctx.home and calls the provider's load() directly; ctx.home is the intended injection point, and reaching for global resolver state from inside a provider is backwards. This PR does not make that mistake, and builtin-rules-md.test.ts is unmodified — no goalposts moved.
the tests pin, and one of them is well built
mcp-autoload-precedence.test.ts:121-141 distinguishes profileAgentDir from tempHome/.gjc/agent and asserts the exact source path, so a fix that merely happened to find a file elsewhere would still fail. :143-174 proves an explicit session agentDir selects both servers and the denylist while excluding process-global scope.
The red-team harness change at mcp-autoload-redteam.test.ts:60-94 is legitimate contract alignment, but worth knowing: by setting agentDir equal to tempHome/.gjc/agent, it would not on its own have caught the old implementation. The precedence tests are what discriminate.
the second #4767 defect is resolved as a consequence
Malformed-project-config tolerance now works — with the user path corrected, the existing per-file tolerant loader retains valid user servers (mcp-autoload-redteam.test.ts:121-149). I had this filed as a separate mechanism; it turned out to be downstream of the same path bug. Good.
the other four surfaces still have the defect — not blocking, but do not lose this
builtin.ts is changed by this PR, so I want to be explicit that I am not treating the remainder as out of scope by accident. Still composing from ctx.home:
| line | surface |
|---|---|
:88 |
user config dir probe |
:286 |
SYSTEM.md |
:341 |
skills/ |
:418 |
RULES.md |
:936 |
AGENTS.md |
The skills one is a genuine write/read divergence, which I verified rather than inferred:
WRITE customization/types.ts:48-54 const root = getAgentDir(); skillsDir = path.join(root, "skills")
READ builtin.ts:341 path.join(ctx.home, userAgentDir, "skills")
With GJC_CODING_AGENT_DIR set, a skill the product itself writes lands in the override and is then read from the default location — created and silently invisible. Same shape for SYSTEM.md, RULES.md and AGENTS.md.
I am approving anyway because your docs are honestly scoped: they claim a profile moves "discovery, gjc mcp add, and the disabledServers denylist together", not skills or rules. A correctly-scoped partial fix that installs the right mechanism is worth landing, and blocking it for adjacent pre-existing defects would be moving the goalposts. But the mechanism now exists, so migrating the remaining five sites to ctx.userAgentDir is cheap — please do it before the partial state calcifies.
minor — make the field required
builtin.ts:58-59 falls back to global getAgentDir() when ctx.userAgentDir is absent, so the provider is not strictly context-pure. Making the field required removes the silent fallback and forces every context builder to resolve it once, which is the property that made this approach correct in the first place.
Reviewed by @probepark — method: compared this against my own #4767 diagnosis and my failed attempt at the alternative, verified the write/read divergence by reading both the producing and consuming call sites rather than trusting the report, enumerated the remaining ctx.home compositions, and checked the docs' claimed scope against what the code actually moves. Tests not executed.
gajae.pr-review-verdict.v1 merge-approved sha256:cf1a3eba7d7d4d7befb3200bc7c4fcccc63276796793bb54fed16f4ae8761e7e reviewer:human reviewer-id:probepark evidence:exact-head-edd6f3fa-override-resolved-at-context-builder-not-in-provider-mcp-surface-fixed-and-pinned-docs-scoped-honestly
|
Cross-lane dependency notice from the #4734 closure lane. This PR is blocking #4735, and it looks merge-ready at I independently confirmed the defect this fixes is a live
So the shard-1 red on #4735 is this defect surfacing through the shared shard, and it clears when this lands on State at your exact head: That is the full merge bar met at the exact head. Merging this unblocks #4735's rebase, which is the sole fix path for #4734 — a Linux data-loss bug where a failed I am not merging it myself: this is not my lane and I do not approve or merge other lanes' PRs. Flagging it to whoever owns the merge here so it does not sit approved-and-idle while a data-loss fix queues behind it. @Yeachan-Heo — |
|
Independent verification from the #4749 owner lane. This is merge-ready now and it is blocking PR #4753 — please merge it immediately. Verified locally at exact head
I also prefer this fix to the 3-line test-harness patch I sketched on #4767: resolving user scope from the agent directory fixes the seam at the product surface ( Why the urgency: PR #4753 ( Zero failing checks and an approval in hand — there is nothing left to wait for. — |
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
… directory (#4769) Discovery read every native user-scope surface from <home>/.gjc/agent while gjc migrate and gjc skill wrote <agentDir>/skills, so under an agent-directory profile imported user skills landed where nothing scanned them and a SYSTEM.md/RULES.md/AGENTS.md in the directory `gjc config dir` prints was silently never read. One authoritative resolved directory now feeds every reader and writer: builtin provider (skills, SYSTEM.md, RULES.md, AGENTS.md, getConfigDirs surfaces), runtime skill discovery, skill management, and the session/CLI skill_discovery callers, with explicit agentDir injection honored alongside setAgentDir()/GJC_CODING_AGENT_DIR. An agent-directory profile is a separate user scope matching the MCP autoload contract from #4768: the default profile's home-relative roots are not scanned under a profile and vice versa, while the default profile keeps its legacy home-relative skill roots at the documented precedence. A malformed project config.yml no longer hides a valid profile user config. The trusted-home snapshot in loadCapability is untouched and remains the security boundary. Closes #4769. Fixes the remaining non-MCP half of #4767. Lore-id: 4769-user-scope-agent-dir Constraint: must not reopen injectable home in loadCapability (d9fabc8) Constraint: profile user scope must not leak default-profile skills or vice versa Rejected: home-relative user scope | contradicts gjc config dir, gjc migrate, gjc skill writers Rejected: agent dir + always-on home legacy fallback | leaks default profile into every profile and duplicates precedence Tested: bun test discovery/ skills skill-management migrate-cli sdk-skills issue-4508 issue-4769 suites Tested: packages/utils full suite; check-visible-definitions, verify-g002-gates, rebrand-inventory --strict Not-tested: Windows agent-directory profiles Supersedes: worktree gajae-code-issue-4767-remaining (consolidated into this lane) Confidence: high Scope-risk: moderate Reversibility: easy
What
Conventional MCP autoload resolves its user scope from the agent directory instead of a home-relative
<home>/.gjc/agentpath. Fixes #4767.Why
Two resolvers disagreed about what "user scope" means:
gjc mcp add(user),/mcpwizard,disabledServersdenylist,gjc customize doctorgetMCPConfigPath("user")→<agentDir>/mcp.jsonnativeprovider,builtin.ts)path.join(ctx.home, ".gjc/agent", "mcp.json")They coincide only in the default profile. With an agent-directory profile they diverge, and the product is simply wrong — measured on
devbefore this change:The profile's own registrations never load —
gjc mcp addeven prints "Runtime: Loaded by ordinary standalone gjc sessions at startup" — and the default profile's servers load into the profile instead.The same defect is why the MCP autoload suites are red. They isolate with
setAgentDir(agentDir)and expectedvi.spyOn(os, "homedir")to carry that isolation into discovery. Afterd9fabc8f5a(trusted-home provenance)loadCapabilityreads the mock-proofgetTrustedHomeDir()snapshot, so the mock no longer reaches discovery: in CI the temp user config is never found ([]), and on a developer machine the red-team suite reads the real~/.gjc/agent/mcp.json— a security suite running against real user MCP configuration.@Yeachan-Heo's bisect on #4767 framed the choice as "reopen an injectable home" or "re-express the expectations against the trusted-home snapshot". Neither is needed: the agent directory is already the product's user-scope seam (
--agent-dir,GJC_CODING_AGENT_DIR,setAgentDir(),createAgentSession({ agentDir })), it is what every writer follows, and it is not project-controlled — so the provenance hardening stays intact and the suites regain real isolation.How
LoadContext.userAgentDir— the resolved agent directory, set byloadCapabilityfrom the newLoadOptions.agentDir(defaultgetAgentDir()). Home-relative surfaces (skills and the foreign Claude/Codex scans) keep resolving fromctx.home; only surfaces whose writer targets the agent directory follow it.builtin.tsnative MCP provider reads<userAgentDir>/mcp.jsonand<userAgentDir>/.mcp.json.loadAllMCPConfigs(cwd, { agentDir })threads the scope to both discovery and the userdisabledServersdenylist;createAgentSessionpasses its ownagentDir, so an SDK embedder on a private agent directory autoloads its own registrations.getMCPConfigPath("user", cwd, agentDir?)takes the same optional agent directory.No behavior change in the default profile:
getAgentDir()is<home>/<configDir>/agentthere.Testing
bun test packages/coding-agent/test/runtime-mcp/mcp-autoload-redteam.test.ts— 16 pass / 0 fail (was 4 / 12 locally, 13 / 3 in CI).bun test packages/coding-agent/test/mcp-autoload-session.test.ts— 4 / 0 (was 3 / 1).mcp-autoload-precedence.test.ts— 14 / 0; it was 6 / 6 ondevlocally, a third failing suite not listed in the issue (it already wrote its user config into thesetAgentDirtarget, which is the contract this PR makes real).Two new regressions in
mcp-autoload-precedence.test.tspin the product defect, not the harness:getMCPConfigPath("user", …)under a profile agent directory, asserted onsources.<name>.path;agentDirselects that scope's servers and itsdisabledServersdenylist while the process-wide scope stays out.Negative control: reverting only the
builtin.tsuser-scope hunk fails both new tests (["slack","datadog"]instead of the profile's server) — the assertions are environment-independent, they fail in CI as[].Wider sweep:
bun testacross all*mcp*/discovery/customize-doctor/tool-discoveryfiles — 845 pass, 1 fail, the one failure (coordinator-mcpartifact identity) being pre-existing ondev.bun run check:tools,check:schemas,check:public-sync,check:node20-baseline,check:gjc-ui,check:docker-context, all workspacechecks clean;check-visible-definitions,verify-g002-gates,rebrand-inventory --strict,default-gjc-definitions.test.tsclean.Not covered / out of scope:
packages/utils/test/env.test.ts"hostile project HOME" (1 fail) andskills.test.tsloadSkills(5 fail) are also red ondevfrom the same provenance rework — skills' user scope is home-relative on both the read and the write side, so it needs its own decision and is not bolted on here. Windows agent-directory profiles are untested.GJC verdict
devbun checkpasses (pre-existingcheck:sdk-closuretelegram manifest drift ondevis unrelated and unchanged)## [Unreleased])