anthropics / anthropics/claude-code

[BUG] IS_DEMO still suppresses the workspace trust prompt in 2.1.263 — all hooks, statusLine and project permissions silently disabled, and /doctor has no trust check

Aperta
#92,911 1 commento 3 reazioni 0 assegnatari Vedi su GitHub
area:hooks area:statusline bug has repro platform:macos
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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?

> **Why a new issue:** this is the same defect as **#37780**, which was closed by the stale bot on 2026-06-26 (`not_planned`, after three `stale` labels — never triaged closed by a human) and whose closing message asks to open a new issue if it is still relevant. The two earlier reports of the same defect, **#10409** and **#34608**, are locked. Happy to close this as a duplicate if #37780 is reopened instead — I have no preference about where it lives, only that it is tracked somewhere. Also related: #84728, #86857, #90220.

Setting `IS_DEMO` in the `env` block of `~/.claude/settings.json` makes the workspace trust prompt **never appear**, so `hasTrustDialogAccepted` stays at its default `false` indefinitely. In that state Claude Code silently skips **every hook**, the `statusLine`, all project-scoped `permissions.allow` entries, and plugin installation — with no error, no warning, and nothing surfaced by `/doctor`.

This was reported in #37780 on 2.1.150 and again on 2.1.233. **It still reproduces on 2.1.263**, and identically on 2.1.261 and 2.1.260.

**Worth being precise about the scope: what gets disabled is the user's own global configuration, not just the folder's.** In a freshly `git init`-ed directory containing only a `README.md` and **no `.claude/` directory at all**, every hook defined in `~/.claude/settings.json` was still skipped (`SessionStart`, `UserPromptSubmit` ×2, `Stop` ×2, `SessionEnd` ×2), and no `Dropped N project-scoped permissions` line appeared because there were none to drop. **And the trust prompt itself never mentions hooks or the statusLine.** I captured it in both cases (screenshots attached below; the username in the path is redacted).

In a repo that has project settings, it adds a warning line about that folder's own configuration:

```
Quick safety check: Is this a project you created or one you trust? ...
Claude Code'll be able to read, edit, and execute files here.

⚠ This folder pre-approves 97 tool permissions in .claude/settings.json and .claude/settings.local.json:
Edit, Write, NotebookEdit, WebSearch, WebFetch, mcp__blender__*, mcp__drawio__*, mcp__ide__*, and 83 more
These will apply without asking. Only proceed if you trust this configuration.
```

In the `git init`-ed folder with no `.claude/` directory, that line is absent and the entire prompt is:

```
Quick safety check: Is this a project you created or one you trust? ...
Claude Code'll be able to read, edit, and execute files here.

Security guide
❯ No, exit
Yes, I trust this folder
```

Every conditional line the prompt can add describes what *that folder* contributes (project permissions, added directories, a `headersHelper`). **Neither wording says anything about the user's own `~/.claude/settings.json` hooks or statusLine — which are exactly what stops running.** A user who reads this prompt carefully still has no way to know.

Note also that the default focus is on `No, exit`, so `Enter` and `Esc` both decline — landing the user in the same silently-degraded state as never being asked, except that `No, exit` at least quits rather than continuing.

I set `IS_DEMO` to keep my account email off the screen while doing educational live-coding sessions — which is the documented, recommended use of the variable. The [environment variables reference](https://code.claude.com/docs/en/env-vars) describes it as:

> Set to any non-empty value, such as `1`, to enable demo mode: hides your email and organization name from the header and `/status` output, and skips onboarding. Setting it to `0` or `false` still enables demo mode, unlike most on/off variables; unset the variable to turn it off. **Useful when streaming or recording a session**

So this is not an internal flag being used out of scope, and it is not marked deprecated. **The documented effects are hiding the email/organization name and skipping onboarding. Suppressing the workspace trust prompt — and with it every hook, the statusLine, and all project-scoped permissions — is not among them.** A user following the documentation for its recommended purpose has no way to know their guardrails stopped running. (See also #84728: it is currently the only way to keep account info off the screen.)

Two things are worth adding to what #37780 already documented.

**1. `git init` silently drops inherited trust, so this recurs for every new repository.** Trust is resolved by walking ancestors, but the walk stops at the git root:

```js
function AB(e,t){ let r=zn(lt(t)); let o=k0(r); // o = git root
let d = o!==null ? y1(lt(o)) : null;
return yB(e, r, d); }
function yB(e,t,r){ let o=y1(t);
while(!0){
if (!(r===null || o===r || o.startsWith(r.endsWith("/")?r:r+"/"))) return !1;
if (e.projects?.[o]?.hasTrustDialogAccepted) return !0;
if (o===r) return !1; // stops at the git root
let p=y1(lt(o,"..")); if (p===o) return !1; o=p;
} }
```

A non-git directory under a trusted parent inherits trust and behaves normally. The moment it becomes a git repository it no longer inherits — and with `IS_DEMO` set, nobody is ever asked. So this does not present as a one-time setup mistake that a user makes and fixes; it silently recurs every time they start a new repo. In my case hooks had been dead for three days across a repo I use daily. (Adjacent to #90220 and #84402, from the opposite direction.)

**2. `/doctor` has no workspace-trust check, so the built-in diagnostic reports a healthy setup.** Run in a directory where every hook is disabled by this bug, `/doctor` produced:

```
## Summary
Setup is largely healthy. Install (2.1.263, native) is current, all config files parse,
and hooks are all fast (slowest 521ms).

### Check 5 — slow hooks
No findings. 347 hook executions recorded in the window, zero timeouts.
```

The `/doctor` instructions contain **zero** references to workspace trust (`workspace trust`: 0 occurrences, `hasTrustDialogAccepted`: 0; the three `trust` matches are all about untrusted *transcript data*). Check 5 measures hook *speed*, and its aggregation window is **50 sessions across 6 projects over 12 days** — so it reported hook activity from *other* projects while the current workspace ran none. The instructions even warn in the safe direction (*"zero recorded runs does not mean the hook rarely fires"*), but nothing checks whether hooks are enabled here at all.

`claude doctor` (the CLI diagnostic) in the same directory prints `No installation issues found.`

Workspace trust being unaccepted is plausibly the highest-impact setup problem `/doctor` could surface, and it is the one thing it does not look at. I am filing this together with the trust bug rather than separately because it is the reason the trust bug stays invisible — a user who suspects something is wrong and runs the built-in health check is told everything is fine.

### What Should Happen?

1. **`IS_DEMO` should not suppress the trust prompt.** Either it should not gate the trust flow, or suppressing the prompt should leave the user something actionable. This is the same pattern as #10409 (`--dangerously-skip-permissions` skipping the trust check without granting trust), which was fixed.
2. **`/doctor` should check workspace trust** and report it as a finding when the current workspace is untrusted, listing what is consequently disabled (hooks, statusLine, project-scoped permissions, plugin installs).
3. **Hook and statusLine skips should be visible at the default log level**, not only under `--debug`. Today the only signal is the absence of whatever the hooks did.

### Error Messages/Logs

```shell
With `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose claude --debug-file /tmp/cc.log` in an untrusted workspace:

[DEBUG] Dropped 97 project-scoped permissions.allow entries — workspace not yet trusted
[DEBUG] Skipping SessionStart:startup hook execution - workspace trust not accepted
[WARN] Status line command skipped: workspace trust not accepted
[DEBUG] Trust not accepted for current directory - skipping plugin installations
[DEBUG] Skipping StatusLine command execution - workspace trust not accepted
[DEBUG] Skipping UserPromptSubmit hook execution - workspace trust not accepted
[DEBUG] Skipping Stop hook execution - workspace trust not accepted
[DEBUG] Skipping SubagentStop hook execution - workspace trust not accepted
[DEBUG] Skipping SessionEnd:prompt_input_exit hook execution - workspace trust not accepted

Relevant implementation in 2.1.263 — `IS_DEMO` short-circuits to `"skip"`, so `"ask"` (the dialog) is unreachable:

function S(){
if (Ie(!1) || Boolean(a.IS_DEMO) || a.CLAUBBIT) return "skip";
return Bo() && !Nae() ? "trusted" : "ask";
}
async function T(r,i,o){ switch(i){
case "skip": if(!a.CLAUBBIT && Bo()){ Dx(!0); ... } ... return; // grants only if already trusted
case "trusted": { Dx(!0); ... return }
case "ask": { ... TrustDialog ... Dx(!0); ... return }
}}
```

### Steps to Reproduce


  1. Put IS_DEMO in env in ~/.claude/settings.json, plus any hook with an observable side effect:
    {
    
    "env": { "IS_DEMO": "true" },
    "hooks": {
    "Stop": [ { "hooks": [ { "type": "command", "command": "echo hi >> /tmp/hook-canary" } ] } ]
    }
    }


  2. mkdir ~/repro && cd ~/repro && git init — a git repository, so trust is not inherited from a trusted parent

  3. CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose claude --debug-file /tmp/cc.log

  4. No trust prompt appears. Send two short messages, then exit.

  5. Observe:

    • /tmp/hook-canary does not exist

    • grep -c 'workspace trust not accepted' /tmp/cc.log → non-zero

    • grep -c 'Getting matching hook commands' /tmp/cc.log0



  6. Run /doctor in the same directory — it reports a healthy setup and never mentions trust

  7. Remove IS_DEMO, restart → the trust prompt appears → accept it → hooks work. Restore IS_DEMO → hooks keep working, because the acceptance persists in ~/.claude.json


Controlled A/B — two freshly git init-ed directories, IS_DEMO set in both, differing only in hasTrustDialogAccepted:

  | hasTrustDialogAccepted | trust prompt | trust not accepted lines | hook dispatches
-- | -- | -- | -- | --
A | false (default) | not shown | 10 | 0
B | true (written directly to ~/.claude.json) | not shown | 0 | 6

Impact


Hook-based guardrails fail open. In my setup a PreToolUse hook blocks commits and pushes to main, and another warns on destructive shell commands. Both scripts pass their own unit tests (10/10 and 21/21) and were simply never invoked for three days. A guard that is silently not running is worse than no guard, because the user believes they are protected.


A UserPromptSubmit hook that warns at 80% context was dead for the same reason, and sessions grew to 965k tokens (96% of a 1M window) with no warning.

### Claude Model

Opus

### Is this a regression?

No, this never worked

### Last Working Version

_No response_

### Claude Code Version

2.1.263 (Claude Code)

### Platform

Anthropic API

### Operating System

macOS

### Terminal/Shell

Other

### Additional Information

- Reproduced in two different terminals, so it is not terminal-specific. Neither is in the Terminal/Shell dropdown, hence "Other".
- Installation method: native (`~/.local/bin/claude` → `~/.local/share/claude/versions/`).
- The `/doctor` figures above come from its own output in the untrusted directory; the full report and both debug logs are available if useful.
- **Screenshots:** the trust prompt in a repo that has project settings (the `pre-approves 97 tool permissions` line is present), and in the `git init`-ed folder with no `.claude/` directory (that line is absent — the whole prompt is the generic file-access sentence). Neither mentions hooks or the statusLine. The username in the paths is redacted.

Image
Image

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start with the reproduction using ~/.claude/settings.json, a fresh git init repository, and CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose claude --debug-file /tmp/cc.log; compare the false/true hasTrustDialogAccepted cases in ~/.claude.json and inspect the trust decision and dispatch paths shown for S and T. Use /doctor and the debug log as checks; done means IS_DEMO no longer suppresses an actionable trust flow, untrusted-workspace consequences are reported, and skipped hooks/statusLine are visible.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
git, javascript, shell
Ambito
cli, developer-experience, security
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.