Skip to content

[BUG] Root cause identified: GrowthBook A/B flags tengu_permission_friction + tengu_quill_harbor silently override defaultMode:bypassPermissions via periodic server sync — macOS Desktop #62205

Description

@amicicixp

Summary

This is a root cause analysis for the cluster of issues where Claude Code Desktop ignores permissions.defaultMode: bypassPermissions and sessions always start in Accept Edits mode. The symptom is already reported in #61415, #61436, #61958, #39523 and many duplicates — but the specific mechanism has not been identified in any of those threads.

Short version: Two GrowthBook feature flags, pushed from Anthropic servers every ~9 minutes, silently override the user's settings.json and lock the Desktop app into Accept Edits mode.


Environment

  • OS: macOS (Darwin 25.x, Apple Silicon)
  • Claude Code Desktop: 1.x (latest)
  • Claude Code CLI: 2.1.149–2.1.151
  • Platform: Anthropic API (personal account)

Root Cause

When a user account is enrolled in the harbor permissions A/B test (tengu_harbor: true, tengu_harbor_permissions: true in ~/.claude.json's cachedGrowthBookFeatures), two additional flags are synced from Anthropic's GrowthBook CDN and cached locally:

Flag 1 — tengu_permission_friction: true

Blocks mode switching entirely. The UI shows the error:

"Permission mode couldn't be changed. You can try again."

even when the user explicitly tries to switch to Bypass Permissions.

Flag 2 — tengu_quill_harbor: "acceptEdits"

Forces every new session to start in Accept Edits mode, regardless of permissions.defaultMode: "bypassPermissions" in settings.json. This is what makes the banner appear on every session start:

"Bypass Permissions mode isn't enabled. The session started in Accept Edits — enable Bypass Permissions in Settings to use it."

The harbor model introduces tengu_quill_harbor as the effective session default, which takes priority over the user-level settings.json defaultMode — effectively treating an A/B test flag as a managed/enterprise policy.


Why the standard workarounds fail

The Allow bypass permissions mode toggle (Settings UI)

Enabling the toggle sets allowDangerouslySkipPermissions: true in settings.json, which maps to --allow-dangerously-skip-permissions (adds bypass to the Shift+Tab cycle). This is not the same as --dangerously-skip-permissions (which actually starts the session in bypass mode). With tengu_permission_friction: true, even cycling via Shift+Tab is blocked.

disableBypassPermissionsMode is NOT set

Checked policy-limits.json and cachedGrowthBookFeatures — disableBypassPermissionsMode is false (or absent). Bypass permissions is not disabled at the organization/managed level. The block comes exclusively from the two A/B test flags above.


Why it keeps coming back

GrowthBook syncs ~/.claude.json from Anthropic's servers approximately every 9 minutes (observed via file modification timestamps across multiple app sessions). Manually setting tengu_permission_friction: false and tengu_quill_harbor: "bypassPermissions" in ~/.claude.json restores correct behavior immediately — but the next GrowthBook sync resets them, reproducing the bug within minutes.

The sync also writes to session-specific cache files in:

~/Library/Application Support/Claude/local-agent-mode-sessions/<session-id>/<workspace-id>/
  cowork-gb-cache.json
  local_<workspace-id>/.claude/.claude.json

These session-level files may have different values than the main ~/.claude.json, causing inconsistent behavior (e.g., subagents running in a different mode than the parent session).


Reproduction

  1. Verify your account is in the harbor A/B test:

    python3 -c "
    import json, os
    d = json.load(open(os.path.expanduser('~/.claude.json')))
    gb = d.get('cachedGrowthBookFeatures', {})
    print('harbor:', gb.get('tengu_harbor'))
    print('harbor_permissions:', gb.get('tengu_harbor_permissions'))
    print('friction:', gb.get('tengu_permission_friction'))
    print('quill_harbor:', gb.get('tengu_quill_harbor'))
    "

    If tengu_harbor: True and tengu_quill_harbor: acceptEdits, you are affected.

  2. Configure ~/.claude/settings.json:

    {
      "permissions": { "defaultMode": "bypassPermissions" },
      "allowDangerouslySkipPermissions": true,
      "skipDangerousModePermissionPrompt": true
    }
  3. Start Claude Code Desktop → new session → send any message.

  4. Expected: session runs in Bypass Permissions.

  5. Actual: banner appears, session is in Accept Edits, mode switch fails.


Temporary workaround

Patch ~/.claude.json after each GrowthBook sync:

import json, os
path = os.path.expanduser('~/.claude.json')
d = json.load(open(path))
gb = d.setdefault('cachedGrowthBookFeatures', {})
gb['tengu_permission_friction'] = False
gb['tengu_quill_harbor'] = 'bypassPermissions'
json.dump(d, open(path, 'w'), ensure_ascii=False, indent=2)

Must be re-applied every time GrowthBook syncs (~9 min). A launchd WatchPaths agent can automate this.


Expected behavior

permissions.defaultMode: "bypassPermissions" in settings.json should be respected regardless of which GrowthBook A/B test cohort the user is enrolled in. A/B tests should not silently downgrade explicitly configured user permissions.


Suggested fix

In the harbor permissions model, when computing the effective session start mode, tengu_quill_harbor should be treated as a default that settings.json's defaultMode can override — not as a mandatory setting. Similarly, tengu_permission_friction should be false for any account where bypass permissions is not disabled at the managed/policy level.


Related issues

Activity

  1. amicicixp commented on May 25, 2026

    @amicicixp
    Author

    Update: additional root cause found in claude_desktop_config.json

    After further investigation, there is a second mechanism contributing to the Desktop-specific failure, complementary to the GrowthBook flag issue described in the original report.

    Finding

    ~/Library/Application Support/Claude/claude_desktop_config.json contains an epitaxyPrefs key that controls the permission mode for new sessions opened from the Desktop app landing page:

    {
      "preferences": {
        "epitaxyPrefs": {
          "cc-landing-draft-permission-mode": "acceptEdits"
        }
      }
    }

    When tengu_quill_harbor: "acceptEdits" is active (GrowthBook sync), the Desktop app writes cc-landing-draft-permission-mode: "acceptEdits" to this file. This persists the wrong mode even between GrowthBook sync cycles — so fixing ~/.claude.json alone is not sufficient if claude_desktop_config.json retains the stale value.

    Community workaround (confirmed working on macOS by multiple users in #61415)

    Adding "bypassPermissionsModeEnabled": true to the preferences object in claude_desktop_config.json acts as a Desktop-app-level override:

    {
      "preferences": {
        "bypassPermissionsModeEnabled": true,
        "epitaxyPrefs": {
          "cc-landing-draft-permission-mode": "bypassPermissions"
        }
      }
    }

    Then relaunch the Desktop app.

    Full fix chain (belt-and-suspenders)

    For complete and persistent resolution, both files need to be corrected:

    File Fix needed
    ~/.claude.json → cachedGrowthBookFeatures tengu_permission_friction: false, tengu_quill_harbor: "bypassPermissions"
    claude_desktop_config.json → preferences bypassPermissionsModeEnabled: true
    claude_desktop_config.json → preferences.epitaxyPrefs cc-landing-draft-permission-mode: "bypassPermissions"

    Since GrowthBook re-syncs ~/.claude.json every ~9 minutes and may trigger the Desktop app to re-write claude_desktop_config.json, a persistent watchdog (launchd WatchPaths on both files) is needed to maintain the fix.

    Note on folder-specific modes

    claude_desktop_config.json also contains per-folder permission mode overrides under epitaxy-folder-permission-mode. These appear to be set by the user via the UI and should not be modified by any automated fix — only the global cc-landing-draft-permission-mode and bypassPermissionsModeEnabled need to be corrected.

  2. github-actions commented on May 25, 2026

    @github-actions
  3. amicicixp commented on May 25, 2026

    @amicicixp
    Author

    Update: 5th attack vector confirmed — per-folder epitaxy-folder-permission-mode

    After further investigation following a fresh restart, I've identified an additional override path that none of the previously known workarounds (including bypassPermissionsModeEnabled in claude_desktop_config.json) cover.

    New vector

    File: ~/Library/Application Support/Claude/claude_desktop_config.json

    {
      "preferences": {
        "epitaxyPrefs": {
          "epitaxy-folder-permission-mode.<accountId>": {
            "/Users/<me>/Downloads": "acceptEdits",    // ← silently set by Desktop app
            "/Users/<me>/some-project": "auto"
          }
        }
      }
    }

    When GrowthBook sync flips tengu_quill_harbor to acceptEdits, the Desktop app persists the per-folder override to epitaxy-folder-permission-mode.<accountId>[<folder>] = "acceptEdits". This per-folder value then takes precedence over both:

    • cc-landing-draft-permission-mode (global default for new sessions)
    • defaultMode: bypassPermissions in ~/.claude/settings.json

    So even after fixing every other known vector, opening a new session from an affected folder still lands in Accept Edits mode and the mode change still fails with the original error.

    Repro

    1. Have GrowthBook sync set tengu_quill_harbor: acceptEdits at least once.
    2. Open a session from a project folder, observe it lands in Accept Edits.
    3. Close the session. Inspect claude_desktop_config.json — the folder is now pinned at "acceptEdits".
    4. Even after correcting tengu_* flags and cc-landing-draft-permission-mode, new sessions from that folder still land in Accept Edits until the per-folder override is cleared.

    Summary — 5 attack vectors total

    # File Field Symptom
    1 ~/.claude.json cachedGrowthBookFeatures.tengu_permission_friction Mode change UI fails
    2 ~/.claude.json cachedGrowthBookFeatures.tengu_quill_harbor Session starts in acceptEdits
    3 claude_desktop_config.json preferences.bypassPermissionsModeEnabled Bypass toggle inert
    4 claude_desktop_config.json preferences.epitaxyPrefs.cc-landing-draft-permission-mode New-session default flipped
    5 claude_desktop_config.json preferences.epitaxyPrefs.epitaxy-folder-permission-mode.<accountId>[folder] Per-folder pin survives all other fixes

    Vector 5 is the most insidious because it persists across restarts and is invisible unless you inspect that specific nested key.

    Suggested fix

    The Desktop app should not write "acceptEdits" to epitaxy-folder-permission-mode when the global setting is bypassPermissions and the user has the bypass gate enabled. Alternatively, a single source of truth (settings.json defaultMode) should be respected, with GrowthBook flags treated as defaults only when no user-set override exists.

  4. raph7seas commented on May 26, 2026

    @raph7seas

    Great analysis — the GrowthBook A/B flag mechanism you identified is the underlying root cause, and your repro is reliable. Adding a complementary workaround for anyone landing here who's tired of patching ~/.claude.json every ~9 minutes:

    You can short-circuit this one layer above GrowthBook by setting the launcher's global gate bypassPermissionsModeEnabled: true in claude_desktop_config.json. It's a local user pref, never re-synced from the server, so it persists indefinitely.

    The launcher logic (from app.asar):

    const d = i.permissionMode ?? r?.permissionMode ?? u ?? sn.Default,  // ← `u` is where the harbor default lands
          h = Bi("bypassPermissionsModeEnabled") === true,
          f = d === sn.Bypass && !h,
          y = f ? sn.AcceptEdits : d;
    // later: d && Y.push("--allow-dangerously-skip-permissions")

    When h === true, the d === sn.Bypass && !h downgrade check short-circuits regardless of what u (the harbor-derived default) resolves to. And the --allow-dangerously-skip-permissions flag actually reaches the spawned CLI, which also defuses the tengu_permission_friction-induced "Permission mode couldn't be changed" error — flipping mode mid-session fails when the CLI was spawned without the flag.

    I'm in the same A/B cohort you describe (tengu_harbor=true, tengu_harbor_permissions=true, tengu_permission_friction=true, tengu_quill_harbor=acceptEdits) and setting bypassPermissionsModeEnabled: true alone defuses both symptoms — no ~/.claude.json patching required.

    Full writeup with macOS paths, the chmod 444 quit-write gotcha, and the account-UUID-after-reinstall caveat: #61415 (comment).

    Doesn't supersede your fix request — Anthropic should still respect defaultMode: bypassPermissions for harbor users — but it gets people unstuck without a launchd watchpath agent in the meantime.

  5. yurukusa commented on May 28, 2026

    @yurukusa

    Operator-side note for fellow Cluster 10 victims, building on @amicicixp's root-cause analysis and 5-vector enumeration.
    I've published a 2,487-word field guide consolidating the cluster shape and the three currently-shipped operator-side hooks: https://gist.github.com/yurukusa/69a3fcd4eac07533276b3103c8b21d9d (MIT).
    What the guide adds beyond this thread:

    1. Three converging axes — periodic re-sync (~9 min), no release boundary, five documented override paths — making the cluster legible as a transport category, not a permission-matcher category. The settings.json contract is correct on the local side; the issue is server-pushed flag values trumping local files.
    2. The five override paths in their operator-discovery order: ~/.claude.json → cachedGrowthBookFeatures, the session-specific cowork-gb-cache.json, local_<workspace-id>/.claude/.claude.json, epitaxy-folder-permission-mode, claude_desktop_config.json. The same flag can have different values on different paths (which explains observed cases where subagents run in a different mode than the parent session).
    3. Three operator-side hooks shipped in cc-safe-setup, all advisory, all observational:
    4. The structural acknowledgment — none of the three hooks prevents the dispatch path itself. The GrowthBook sync runs on a background timer that no hook surface observes; by the time the next SessionStart/PreToolUse/Stop hook fires, the override has already taken effect. The shipped hooks make the result visible. Only a network-level block (host-file entry routing the GrowthBook CDN to 127.0.0.1) prevents the cause, and that's hostile to the rest of the application.
    5. What an upstream fix would look like — tengu_quill_harbor should be a default that settings.json's defaultMode can override, not a mandatory setting; experiment cohort allocation should not appear in the same code path as managed policy fields like disableBypassPermissionsMode. The thread's reporter already named this; the guide formalizes it as a policy-boundary suggestion.
      The cluster framing (HTML, links to all three hook PRs): https://github.com/yurukusa/cc-safe-setup/blob/main/docs/cluster-tracker.html#cluster-growthbook
      I tracked this issue's progression for the Cluster 10 framing because the root-cause depth here is rare in the bug tracker — the @amicicixp three-comment chain (initial analysis → second mechanism in claude_desktop_config.json → 5th vector at epitaxy-folder-permission-mode) is the kind of investigation that makes a real cluster, not a duplicate pile-on. Thank you for documenting all five vectors publicly.
      If anyone has reproductions on Linux specifically (~/.config/Claude/cachedGrowthBookFeatures), I'd be interested to know whether the sync cadence is the same ~9 minutes there.
  6. xxparrotxx commented on Jun 17, 2026

    @xxparrotxx

    Confirming this on Windows 11, Desktop 1.13576.0, npm @anthropic-ai/claude-code 2.1.158.

    Same mechanism, additional data points:

    • Remote Control sessions launched from the CLI as claude --remote-control --dangerously-skip-permissions
      come up in acceptEdits on the phone, not bypassPermissions.
    • The launch flag IS honored locally. A/B headless test, same CLI build:
      claude -p ... --dangerously-skip-permissions -> "permissionMode":"bypassPermissions"
      claude -p ... --permission-mode bypassPermissions --allow-dangerously-skip-permissions -> "permissionMode":"bypassPermissions"
      Both record bypassPermissions in the transcript. So the downgrade is NOT in the CLI; it is applied on
      the remote/relay path that the mobile app connects through.
    • On this machine: ~/.claude.json cachedGrowthBookFeatures has "tengu_quill_harbor": "acceptEdits" and
      "tengu_permission_friction": true, and %APPDATA%\Claude\claude_desktop_config.json has
      epitaxyPrefs."cc-landing-draft-permission-mode": "acceptEdits" (despite bypassPermissionsModeEnabled: true
      in the same file). tengu_disable_bypass_permissions_mode is false, so bypass is not disabled, only overridden.
    • This is platform-independent (the flag is account-level), so the macOS-only label undersells it. Please
      have remote-control sessions honor --dangerously-skip-permissions, or make the GrowthBook override
      user-overridable.
  7. Heazer13 commented on Jul 21, 2026

    @Heazer13

    Adding another confirmed report — same symptoms, different angle.

    Claude Max subscriber, Claude Desktop macOS, Claude Code v2.1.204+. My settings.json has permissions.defaultMode set (tested both "acceptEdits" and "bypassPermissions"), and it silently reverts to manual/friction mode roughly every ~9 minutes. This makes scheduled/autonomous tasks configured with bypassPermissions stop running unattended without any warning.

    Workaround I'm using in the meantime: setting affected scheduled-task SKILL.md files' permission-mode to acceptEdits instead of bypassPermissions, since that mode seems to survive the resync (unlike bypass).

    Given this affects unattended/scheduled workflows specifically, would appreciate an update on whether accounts can be opted out of this GrowthBook test, or whether settings.json will be made authoritative over the server-synced flags. Happy to share diagnostic details if useful.

  8. bcherny commented on Aug 19, 2026

    @bcherny
    Collaborator

    Closing as a duplicate of #61415 — please follow and 👍 that issue instead so we can track it in one place. If you think this was closed in error, comment here and we'll reopen.

    🤖 Generated with Claude Code

  9. github-actions commented on Oct 10, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  10. locked as resolved and limited conversation to collaborators on Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:desktoparea:permissionsbugSomething isn't workingduplicateThis issue or pull request already existshas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOSstaleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions