[BUG] Workspace trust is granted by ancestor walk but consumed by exact match for extraKnownMarketplaces, silently disabling project-scoped marketplace config
Environment
- Claude Code 2.1.222, native install, macOS 15 (darwin 25.5.0)
Summary
Workspace trust is granted by walking up the directory tree, but consumed by exact path match when deciding whether to honor extraKnownMarketplaces from project settings. The two disagree, so accepting the trust dialog once at a parent directory permanently and silently disables project-scoped marketplace configuration in every repository underneath it -- with no prompt, no warning, and no log line.
The asymmetry
Granting / prompt-gating walks ancestors:
function sUb(){
if (te.CLAUDE_CODE_SANDBOXED) return true;
if (nxe()) return true;
if (vs()) return true;
let e = Lt(), t = ove();
if (e.projects?.[t]?.hasTrustDialogAccepted) return true;
let n = X2e(Mt());
while (true) { // ancestor walk
if (e.projects?.[n]?.hasTrustDialogAccepted) return true;
let i = X2e(Cb.resolve(n, "..")); if (i === n) break; n = i;
}
return false;
}
(Cqe() does the same walk, with an exact-match probe first.)
Consumption for marketplaces is exact-match only:
function hF(e){ return uCr(e ?? pn()) }
function uCr(e){ let t = Gbn(e); return Lt().projects?.[t]?.hasTrustDialogAccepted === true }
function mne(){
let r = Nwo(); // -> hF() -> uCr(), exact match
let n = r ? co().extraKnownMarketplaces ?? {} : tB_();
return { ...e, ...(r ? BCs() : {}), ...n }
}
function tB_(){
for (let t of mw()) {
if (eB_.has(t)) continue; // eB_ = {"projectSettings","localSettings"}
...
}
}
Resulting failure
- A user accepts the trust dialog once at a parent directory --
~/Projects,~/src,~/code. Common, and encouraged by the fact that the dialog appears per directory. - In every repo underneath,
sUb()returns true via the ancestor walk, so the trust dialog never appears again. - Because it never appears,
mvt()never writeshasTrustDialogAcceptedfor those child paths, and they retain the defaultfalse. Nwo()therefore evaluates false in every child repo, somne()takes thetB_()branch, which excludesprojectSettingsandlocalSettings.- Any
extraKnownMarketplacescommitted to a repo's.claude/settings.jsonis silently discarded.
Observed on this machine: ~/.claude.json has hasTrustDialogAccepted: true for /Users/<me>/Projects and false for /Users/<me>/Projects/<repo>. The false means "never asked", not "asked and declined" -- the user was never given the opportunity to trust the child path, and there is no UI affordance to request the prompt again.
Why it is hard to notice
A repo's committed .claude/settings.json is partially honored, which makes the failure look impossible:
- committed
hooksexecute normally (verified: aSessionEndhook in project settings ran to completion, status 0) - committed
permissions.denyrules apply normally (verified in the debug log) - committed
enabledPluginsis read normally - committed
extraKnownMarketplacesis discarded
Hooks and permissions consult the ancestor-walking predicate; marketplaces consult the exact-match one. So "this directory is trusted" means two different things depending on which subsystem asks, from the same settings file.
The symptom is an enabledPlugins entry that can never resolve:
[DEBUG] Skipping orphaned enabledPlugins entry <plugin>@<marketplace>: marketplace not registered
That line names the plugin but not the cause. Nothing states that the marketplace declaration was dropped for trust reasons. There is a string in the bundle for a comparable case elsewhere ("Ignoring N entries from <file>: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects[\"<path>\"].hasTrustDialogAccepted: true"), but it was never emitted in any of seven captured --debug-file logs across print, interactive, trusted-cwd and untrusted-cwd sessions -- so whatever it covers, it is not this path.
Requested change
Any of:
- Use the same ancestor-walking predicate for
extraKnownMarketplacesthat hooks and permissions already use, so one settings file is honored consistently. - Or, if exact match is deliberate for marketplaces (a plausible security argument -- marketplace config causes code to be fetched and executed, so inheriting trust from an ancestor may be too permissive), then say so at runtime: emit the existing "not been trusted" diagnostic when marketplace entries are dropped, and re-prompt for the child path rather than silently skipping.
- At minimum, document that
extraKnownMarketplacesis not read from project or local settings. The settings documentation lists it among project-settings keys with no such caveat, which is what led us to commit it to a shared repo and assume it worked.
Option 2 seems best: keep the stricter check, but make it observable and recoverable.
Reproduction
mkdir -p ~/tmp-trust-demo/child && cd ~/tmp-trust-demoand start Claude Code; accept the trust dialog.cd child, add.claude/settings.jsoncontaining anextraKnownMarketplacesentry plus a matchingenabledPluginsentry.- Start Claude Code in
child-- note that no trust dialog appears (ancestor grant). - Observe the marketplace is never registered;
known_marketplaces.jsonis unchanged;--debugshows onlySkipping orphaned enabledPlugins entry ...: marketplace not registered, with no explanation. - Set
projects["<abs path to child>"].hasTrustDialogAccepted: truein~/.claude.jsonand restart -- the marketplace now registers.
Step 3 is the crux: the user is never offered the trust decision that step 5 turns out to require.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗