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 writes hasTrustDialogAccepted for those child paths, and they retain the default false.
Nwo() therefore evaluates false in every child repo, so mne() takes the tB_() branch, which excludes projectSettings and localSettings.
- Any
extraKnownMarketplaces committed to a repo's .claude/settings.json is 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
hooks execute normally (verified: a SessionEnd hook in project settings ran to completion, status 0)
- committed
permissions.deny rules apply normally (verified in the debug log)
- committed
enabledPlugins is read normally
- committed
extraKnownMarketplaces is 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 : 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
extraKnownMarketplaces that 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
extraKnownMarketplaces is 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-demo and start Claude Code; accept the trust dialog.
cd child, add .claude/settings.json containing an extraKnownMarketplaces entry plus a matching enabledPlugins entry.
- Start Claude Code in
child -- note that no trust dialog appears (ancestor grant).
- Observe the marketplace is never registered;
known_marketplaces.json is unchanged; --debug shows only Skipping orphaned enabledPlugins entry ...: marketplace not registered, with no explanation.
- Set
projects["<abs path to child>"].hasTrustDialogAccepted: true in ~/.claude.json and 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.
Environment
Summary
Workspace trust is granted by walking up the directory tree, but consumed by exact path match when deciding whether to honor
extraKnownMarketplacesfrom 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:
(
Cqe()does the same walk, with an exact-match probe first.)Consumption for marketplaces is exact-match only:
Resulting failure
~/Projects,~/src,~/code. Common, and encouraged by the fact that the dialog appears per directory.sUb()returns true via the ancestor walk, so the trust dialog never appears again.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.extraKnownMarketplacescommitted to a repo's.claude/settings.jsonis silently discarded.Observed on this machine:
~/.claude.jsonhashasTrustDialogAccepted: truefor/Users/<me>/Projectsandfalsefor/Users/<me>/Projects/<repo>. Thefalsemeans "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.jsonis partially honored, which makes the failure look impossible:hooksexecute normally (verified: aSessionEndhook in project settings ran to completion, status 0)permissions.denyrules apply normally (verified in the debug log)enabledPluginsis read normallyextraKnownMarketplacesis discardedHooks 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
enabledPluginsentry that can never resolve: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 : 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-filelogs across print, interactive, trusted-cwd and untrusted-cwd sessions -- so whatever it covers, it is not this path.Requested change
Any of:
extraKnownMarketplacesthat hooks and permissions already use, so one settings file is honored consistently.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.child-- note that no trust dialog appears (ancestor grant).known_marketplaces.jsonis unchanged;--debugshows onlySkipping orphaned enabledPlugins entry ...: marketplace not registered, with no explanation.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.