[BUG] hasTrustDialogAccepted never persisted to ~/.claude.json despite repeated interactive sessions

Status Fixed / completed
Reported on v2.1.193
Maintainer reply None cached
Activity 12 comments · opened Jun 26, 2026 · closed Aug 20, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

After months of interactive use in a project directory, the hasTrustDialogAccepted flag in ~/.claude.json remains false. On every new session, Claude Code warns:

Ignoring 20 permissions.allow entries from .claude/settings.local.json:
this workspace has not been trusted. Run Claude Code interactively here
once and accept the trust dialog, or set
projects["/path/to/project"].hasTrustDialogAccepted: true in ~/.claude.json.

Flag stays false indefinitely. projectOnboardingSeenCount increments correctly (value: 4), proving the project was opened interactively multiple times - but the trust acceptance is never written.

Environment:

  • OS: Linux (Debian 13, kernel 6.12)
  • Claude Code: 2.1.193
  • Project path: /home/username/workdir/projectname

What Should Happen?

After accepting the trust dialog once, hasTrustDialogAccepted is set to true and persists across sessions.

Error Messages/Logs

Ignoring 20 permissions.allow entries from .claude/settings.local.json:
this workspace has not been trusted. Run Claude Code interactively here
once and accept the trust dialog, or set
projects["/path/to/project"].hasTrustDialogAccepted: true in ~/.claude.json.

Steps to Reproduce

  1. Open a project directory with Claude Code interactively
  2. Proceed through any trust/onboarding dialog
  3. Close and reopen the project multiple times
  4. Observe hasTrustDialogAccepted in ~/.claude.json

Claude Model

Sonnet (default)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

version before latest

Claude Code Version

2.1.193

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Workaround:

Manually set hasTrustDialogAccepted: true in ~/.claude.json.

View original on GitHub ↗

11 Comments

yurukusa · 2 months ago

The detail that projectOnboardingSeenCount increments to 4 while hasTrustDialogAccepted stays false is actually the most useful clue here, because it rules out the explanation people usually reach for first.
What it rules out — path-key mismatch. Both fields live under the same object: projects["/home/username/workdir/projectname"]. If the onboarding counter is incrementing, then Claude Code is locating and writing that exact project key on every run. So this is not the classic "the path is normalized differently on write vs. read (symlink / realpath / trailing slash) so it keeps creating a fresh entry" bug. The write is hitting the right key; only the trust field isn't being set.
That narrows it to a few concrete things worth checking, in order:

  1. **Is the trust dialog actually being accepted, or dismissed/auto-skipped?** projectOnboardingSeenCount increments when onboarding is shown, which is not the same event as accepting trust. If your terminal/session is being launched in a way that bypasses or auto-dismisses the trust prompt (e.g. piped input, a wrapper, non-interactive-ish TTY, or pressing Esc/Enter past it), the counter rises but the acceptance write never fires. Worth confirming you're seeing and explicitly confirming the trust dialog, not the onboarding tour.
  2. Two config files (the most common real cause). If CLAUDE_CONFIG_DIR is set (or differs between how you launch interactively vs. how the warning-emitting session launches), the dialog persists to one ~/.claude.json-equivalent while the trust check reads another. Check:

``
echo "$CLAUDE_CONFIG_DIR"
ls -la ~/.claude.json
``

  1. Is ~/.claude.json actually writable and not being reverted? A dotfile sync (chezmoi/yadm/stow), a read-only mount, or a process that rewrites it can silently undo the write. Confirm the whole file isn't being restored between sessions — onboardingSeenCount changing proves writes land, but a sync that restores a snapshot afterward would clear hasTrustDialogAccepted again. stat ~/.claude.json before/after a session and diff.

Immediate unblock (the warning already hints at it, and it persists fine once set by hand):

jq '.projects["/home/username/workdir/projectname"].hasTrustDialogAccepted = true' \
  ~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.json

Use the exact path string the warning prints. If you set it manually and it also reverts to false next session, that's strong evidence for cause #3 (something external is rewriting the file) and is the single most useful thing to report back to narrow it for the maintainers.
(I'm inferring the mechanism from the asymmetry you reported rather than having reproduced your exact setup — but the manual set is safe and reversible, and whether it sticks is the test that tells you which of the three it is.)

SecBurg · 2 months ago

Hi @yurukusa ,

thanks for investigating!

To answer your questions:

  1. trust dialog is _accepted_
  2. echo "$CLAUDE_CONFIG_DIR" <- outputs nothing (as expected, I don't set this variable anywhere),
  3. ~/.claude.json is writable and is NOT synced by any tools:

before session:

stat ~/.claude.json  
  File: /home/username/.claude.json
  Size: 90613           Blocks: 184        IO Block: 4096   regular file
Device: 8,1     Inode: 16258919    Links: 1
Access: (0600/-rw-------)  Uid: ( 1000/    username)   Gid: ( 1000/    username)
Access: 2026-06-26 18:00:52.064409380 +0200
Modify: 2026-06-26 18:00:52.064409380 +0200
Change: 2026-06-26 18:00:52.068409385 +0200
 Birth: 2026-06-26 18:00:52.064409380 +0200

after session:

stat ~/.claude.json 
  File: /home/username/.claude.json
  Size: 90060           Blocks: 176        IO Block: 4096   regular file
Device: 8,1     Inode: 16258919    Links: 1
Access: (0600/-rw-------)  Uid: ( 1000/    username)   Gid: ( 1000/    username)
Access: 2026-06-26 18:02:16.464483869 +0200
Modify: 2026-06-26 18:02:16.464483869 +0200
Change: 2026-06-26 18:02:16.468483872 +0200
 Birth: 2026-06-26 18:02:16.464483869 +0200
eparis · 2 months ago

I'm having the same message - though on startup claude isn't asking me to trust it and projectOnboardingSeenCount stays 0. I know .claude.json is writable, because the directory/project shows up in the file at all. So mine is even worse.

abkrim · 2 months ago

Same symptom on macOS (darwin 25.5.0), Claude Code 2.1.195 — so this isn't Linux-specific. I think I can answer @yurukusa's cause #3 ("something external is rewriting the file"): the external process is another concurrent claude session.

Mechanism

~/.claude.json is a single global file that every running session loads into memory at startup and rewrites. A session that started while a project was untrusted holds hasTrustDialogAccepted: false for that project in memory, and re-asserts it on a later write/shutdown — clobbering a true that was set by the dialog in another session, or by hand. With several long-lived sessions, the per-project trust flag becomes effectively last-writer-wins.

This explains @SecBurg's evidence directly: the file size changed between sessions (90613 → 90060) with no sync tool involved — that write came from a different claude instance.

Important nuance (what it is not)

Routine telemetry writes (lastCost, lastDuration, lastSessionId, …) appear to be read-modify-write that preserves untouched fields: I set hasTrustDialogAccepted: true externally, then a session that had booted with false in memory wrote its telemetry and preserved the true. So the revert is not caused by routine telemetry. It is caused by sessions that booted with a stale false re-asserting it at specific events (startup / shutdown / trust re-eval).

The other half — the dialog never reappears once onboarded

For projects with hasCompletedProjectOnboarding: true and hasTrustDialogAccepted: false, the trust dialog is never shown again, so there is no UI path to recover — claude just prints the "Ignoring N permissions…" warning forever (relevant to @eparis's "claude isn't asking me to trust it"). The re-prompt seems gated behind onboarding. The only fix is editing the JSON.

Reproduction / confirmation

  • With 2+ sessions alive, set the flag with jq → it reverts.
  • With every claude process closed, set the flag → it sticks (verified across 4 fresh sessions afterward).

Scale on my machine

60 of 119 project entries sit at false, with no correlation to onboarding (trust×onboarding 2×2 is roughly flat: 31 / 28 / 29 / 31) — consistent with accumulation from multi-session write races rather than a per-project condition.

Workaround

pgrep -x claude        # must be empty — close ALL sessions first
cp ~/.claude.json ~/.claude.json.bak.$(date +%Y%m%d-%H%M%S)
jq '.projects[].hasTrustDialogAccepted = true' ~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.json

Suggested fixes

  1. Concurrency-safe writes to ~/.claude.json: advisory lock / atomic field-level update, or re-read the on-disk trust flag immediately before persisting instead of writing the in-memory snapshot's value. (Splitting volatile per-session telemetry from durable config would also remove the race surface.)
  2. Decouple the trust re-prompt from hasCompletedProjectOnboarding, and/or add a /trust command to (re)affirm trust from inside a session.

Likely the same root cause as #65451 (closed as duplicate — "workspace trust wiped by concurrent writes") and #70501.

eparis · 2 months ago

Ok - I think I found at least part of MY bug. I was working on a project in /home/eparis/Code/project. Inside .claude.json I also found that I had projects entries for /home/eparis and for /home/eparis/Code. Removing all 3 of these (nested) projects from the json resulted in claude asking me the security dialog on startup inside project without the error/complaint saying I needed to accept in interactive mode. I ended up with both hasCompletedProjectOnboarding and hasTrustDialogAccepted True. Before I removed the parent projects every time I deleted and recreated the leaf org project I would not be asked the security dialog, I would end up with '"hasCompletedProject Onboarding": true "projectOnboardingSeenCount": 0 and "hasTrustDialogAccepted": false`.

jaded423 · 2 months ago

Confirming this on macOS (Darwin 25.5.0), Claude Code 2.1.196 — same bug, different platform, so it's not OS-specific. I filed #72507 before finding this; closing that as a duplicate and consolidating here.

Likely root cause: the trust dialog never re-fires because onboarding is already marked complete. On affected projects:

  • hasCompletedProjectOnboarding: true
  • projectOnboardingSeenCount: 4 (same value reported here)
  • hasTrustDialogAccepted: false
  • lastOnboardingVersion: 2.1.59 — onboarding completed before trust enforcement existed

Because onboarding is flagged done, the interactive trust prompt is skipped on every launch, so there's no way to set hasTrustDialogAccepted through the UI — the CLI's own suggested remedy ("accept the trust dialog") is unreachable. Enforcement of the flag is new in 2.1.195+; the flag itself was simply never written for pre-enforcement projects.

Evidence it's enforcement, not data loss: my .claude.json backups from March and May also show the flag false for these dirs. Nothing flipped — the value was always false; 2.1.195+ just started enforcing it. Affected ~49 of 68 projects on this machine.

Suggested fix: either (a) re-fire the trust dialog whenever hasTrustDialogAccepted is false regardless of onboarding state, or (b) migrate already-onboarded projects (that had a working settings.local.json allow list pre-2.1.195) to hasTrustDialogAccepted: true.

Workaround: with all Claude sessions closed (Claude rewrites ~/.claude.json on exit and will clobber a live edit), set hasTrustDialogAccepted: true on each project entry in ~/.claude.json, then relaunch.

framoz · 2 months ago

Still reproduces on 2.1.196 (Linux), so the regression isn't fixed yet.

Accepting the trust dialog never writes hasTrustDialogAccepted: true back to ~/.claude.json. In my case the project entry stays at hasTrustDialogAccepted: false while projectOnboardingSeenCount has climbed to 4, so the dialog is clearly being shown and accepted on each session, it just isn't being persisted. Every new session then ignores the ~27 permissions.allow entries in that project's .claude/settings.local.json and warns that the workspace hasn't been trusted.

Manually setting hasTrustDialogAccepted: true for the project in ~/.claude.json still works as a stopgap, but a fresh accept never makes it stick on its own.

CommitMaster102 · 2 months ago

Another reproduction path that lands in the same hasCompletedProjectOnboarding: true / hasTrustDialogAccepted: false state, in case it helps pin down the gate:

First-ever launch of a folder with --permission-mode bypassPermissions. That initial bypass launch stamps hasCompletedProjectOnboarding: true but never sets hasTrustDialogAccepted, so it stays false. On every later plain claude, the trust dialog is skipped (onboarding already complete) and the permissions.allow entries are ignored with the usual warning. The difference from this report is that the dialog was never shown even once, rather than shown-and-not-persisted — but the resulting stuck state and the workaround are identical.

  • Claude Code 2.1.197, Windows 11, Git Bash.
  • Reproduced from a clean state (no prior ~/.claude.json entry for the folder). The project entry is born with onboarding: true + trust: false.
  • Same workaround: manually set hasTrustDialogAccepted: true in ~/.claude.json with all sessions closed.

Taken together with #72507 (pre-2.1.195 onboarding), the common root cause looks like the trust re-prompt being gated on hasCompletedProjectOnboarding instead of on hasTrustDialogAccepted: any path that completes onboarding without setting the trust flag becomes unrecoverable through the UI.

(I filed #72610 specifically for this bypassPermissions trigger; closing it as a duplicate of this one.)

kiki830621 · 2 months ago

Reproducing on macOS (Darwin 27.0.0), Claude Code 2.1.197 — adding population-level evidence from a 300-project ~/.claude.json that may help pin down the gate.

Confirming the bypassPermissions first-launch path (as @CommitMaster102 noted): a project whose first-ever launch was under --dangerously-skip-permissions lands in hasCompletedProjectOnboarding: true, projectOnboardingSeenCount: 0, hasTrustDialogAccepted: false. On every later plain claude (with or without the flag) the trust dialog never re-fires and the Ignoring N permissions.allow entries… warning prints. The CLI's own "accept the trust dialog" remedy is unreachable — only hand-editing ~/.claude.json or downgrading below 2.1.195 works.

Population-level signal. Cross-tabbing all 300 projects in my ~/.claude.json by lastVersionBase × hasTrustDialogAccepted:

| lastVersionBase | trusted | untrusted | untrusted rate |
|---|---|---|---|
| ≥ 2.1.195 | 0 | 14 | 100% |
| < 2.1.195 | 14 | 40 | 74% |
| absent | 58 | 174 | 75% |

Every project last run under 2.1.195+ is untrusted (0/14) — including high-interaction ones (one has projectOnboardingSeenCount: 48 yet hasTrustDialogAccepted: false). If the dialog were firing, those would have been accepted long ago. Consistent with the "onboarding marked complete → dialog never re-fires" root cause: of the 228 untrusted projects, 101 have hasCompletedProjectOnboarding: true.

Re @eparis's nested-entry finding: my ~/.claude.json also has a parent /Users/<me> project entry above the affected subdirectory — consistent with nesting contributing.

Not filing a new issue — same bug, consolidating here per the dup chain (#72507 → this).

G-Eskayo · 1 month ago

Confirming this on macOS with a controlled A/B repro that may help narrow the root cause.

Environment: Claude Code 2.1.200, macOS, Apple Silicon.

Symptom matches exactly: hasTrustDialogAccepted stuck false after multiple clean interactive accepts (lastGracefulShutdown: true each time), projectOnboardingSeenCount incrementing correctly (also landed on 4 here — same as OP).

Ruled out the concurrent-session clobber mechanism from #73364: at the moment of the failing accept, ps aux showed zero other claude processes running (no VS Code extension, no desktop app, no other CLI session, no scheduled background job active). So this reproduces even with a single session and no concurrent writers.

A/B control that narrows it further: the same accept→exit procedure, same machine, same Claude Code process:

  • A brand-new, never-before-seen directory → hasTrustDialogAccepted: true persisted correctly on the very first accept.
  • $HOME and several long-lived project directories under it (all with hasCompletedProjectOnboarding: true from much earlier sessions/versions) → stayed false across two separate clean accepts.

This suggests the write may be conditional on (or skipped for) directories where onboarding was already marked complete before this bug surfaced — i.e. the persistence path for a fresh trust decision works, but re-persisting/updating trust for an already-onboarded project silently no-ops. Might be worth checking whether the flush logic treats hasCompletedProjectOnboarding: true as "nothing left to write" for that project and skips the trust field specifically.

Workaround confirmed: same as OP's — manually setting hasTrustDialogAccepted: true in ~/.claude.json for the affected paths (back up the file first) is safe once you've genuinely accepted the real dialog, and immediately unblocks trust-gated features like claude remote-control.

yurukusa · 1 month ago

@SecBurg Thanks for answering all three questions so thoroughly — and apologies for the slow follow-up on my side.
One data point that may narrow this down: on my environment (WSL2, long-running sessions) the flag does persist, but it lives per-project, not at the top level:

$ jq '.projects."/path/to/your/project".hasTrustDialogAccepted' ~/.claude.json
true

All 12 project entries here have it set. So two things worth checking on your side:

  1. Are you looking for the key at the top level of ~/.claude.json? It won't be there — it's under projects."<absolute project path>".
  2. If it's genuinely absent under your project's entry while projectOnboardingSeenCount increments, the write path works and the bug is specific to that field. A before/after diff of the file across one accept would show exactly what the dialog writes in your case.

Since @framoz reports it still reproducing on 2.1.196, the diff output would be useful evidence for the maintainers either way.

Showing cached comments. Read the full discussion on GitHub ↗