anthropics / anthropics/claude-code

`.claude.json` stores one project directory under up to three different path spellings, splitting trust, MCP servers, and worktree state across them

Abierto
#88,418 5 comentarios 0 reacciones 0 asignados Ver en GitHub
area:core area:mcp area:security bug has repro platform:windows
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

## Summary

On Windows, `projects` in `.claude.json` is keyed by a raw path string with **no
normalization**. Different writers produce different spellings of the *same* directory, so a
single project ends up stored as two or three independent records that never merge.

The records are not equivalent. The backslash-spelled one carries `hasTrustDialogAccepted:
true`; the forward-slash ones carry `hasTrustDialogAccepted: false` and are the only ones that
ever hold `mcpServers`, `hasUnseenTeamArtifacts`, and `activeWorktreeSession`.

The user-visible symptom is that a project appears to "lose" its configured MCP servers and its
active worktree, and re-prompts for trust it was already granted.

## Environment

| | |
|---|---|
| OS | Windows 11 Pro 10.0.26200 |
| Claude Desktop | `1.25927.0` (Squirrel, `%LOCALAPPDATA%\AnthropicClaude`) |
| Config roots inspected | 5 independent ones, via `CLAUDE_CONFIG_DIR` |

Not related to #86012 (cross-session messaging) or #86556 (Squirrel roll-forward). This is a
data-model defect in `.claude.json` and reproduces with a single config root.

## The three spellings

All three observed for one directory, in one config file:

```
c:/Users//Code/ lowercase drive, forward slash
C:/Users//Code/ uppercase drive, forward slash
C:\Users\\Code\ uppercase drive, backslash
```

## Evidence

Read-only. Run against your own config; it prints only key names and counts.

```powershell
$f = Join-Path $(if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { "$HOME\.claude" }) '.claude.json'
$o = Get-Content $f -Raw | ConvertFrom-Json -AsHashtable
$byNorm = @{}
foreach ($k in $o.projects.Keys) {
$nk = $k.Replace('\','/').ToLowerInvariant()
if (-not $byNorm.ContainsKey($nk)) { $byNorm[$nk] = @() }
$byNorm[$nk] += $k
}
foreach ($nk in ($byNorm.Keys | Sort-Object)) {
if ($byNorm[$nk].Count -lt 2) { continue }
"=== $nk (stored $($byNorm[$nk].Count) times) ==="
foreach ($k in $byNorm[$nk]) {
$p = $o.projects[$k]
" {0,-46} trustAccepted={1,-6} mcpServers={2,-7} activeWorktreeSession={3}" -f `
$k,
$(if ($p.ContainsKey('hasTrustDialogAccepted')) { $p.hasTrustDialogAccepted } else { 'n/a' }),
$(if ($p.ContainsKey('mcpServers')) { @($p.mcpServers.Keys).Count } else { 'ABSENT' }),
$(if ($p.ContainsKey('activeWorktreeSession')) { 'YES' } else { 'absent' })
}
}
```

### Output, one directory stored three times

```
=== c:/users//code/ (stored 3 times) ===
c:/Users//Code/ trustAccepted=False mcpServers=0 activeWorktreeSession=YES
C:\Users\\Code\ trustAccepted=True mcpServers=ABSENT activeWorktreeSession=absent
C:/Users//Code/ trustAccepted=False mcpServers=0 activeWorktreeSession=YES
```

`hasTrustDialogAccepted` is `true` on exactly the spelling that has no `mcpServers` key, and
`false` on the two that do.

### The split is total, not a tendency

Across 5 independent config roots, 46 project keys, 7 colliding directories:

| key spelling | entries | carry `mcpServers` / `hasUnseenTeamArtifacts` / `activeWorktreeSession` |
|---|---|---|
| forward slash | 17 | **17 of 17** |
| backslash | 29 | **0 of 29** |

Every config root that has any backslash entry shows the same trust inversion: backslash
`true`, forward-slash `false`, same directory.

## Impact

1. **Trust state is not reliably keyed to a directory.** `hasTrustDialogAccepted` is recorded
per spelling. A directory the user has already trusted reads as untrusted under a different
spelling of the same path. This is a security-relevant control keyed on an unnormalized
string, and it fails in both directions: a redundant prompt is merely annoying, but a trust
decision made for one spelling silently not applying to another is the direction that
matters, and neither is visible to the user.
2. **Configured MCP servers disappear.** `mcpServers` exists only on forward-slash entries. A
session resolving the backslash spelling sees none.
3. **Active worktree is lost.** `activeWorktreeSession` exists only on forward-slash entries. A
session resolving the backslash spelling cannot tell which worktree it was working in. This
is the "my project lost its folders" symptom, and it is how the defect actually reaches a
user.
4. **Silent.** Nothing warns that one directory holds multiple records. The file stays valid
JSON and the duplicate keys differ only by case and separator.

Note that `ConvertFrom-Json` (without `-AsHashtable`) **throws** on these files, because
`c:/...` and `C:/...` collide under .NET's case-insensitive property handling. Any tooling that
parses `.claude.json` case-insensitively will fail outright rather than degrade.

## Expected behaviour

Normalize the project path before using it as a key — canonical separator and canonical drive
letter case on Windows — and migrate existing entries by merging colliding records. Failing
that, at minimum key trust state on a normalized path, since that one is a security control.

## Not reproducible on demand

I cannot give steps that produce a second spelling, because I do not know which code path writes
which form. What I can give is that it is present in **5 of 5** config roots on this machine,
affecting 7 distinct directories, with a completely consistent signature.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

Run the provided PowerShell inspection against your own .claude.json and confirm how duplicate project keys and their fields differ. Trace the code paths that write project keys, then verify that Windows spellings normalize to one record and that existing colliding entries retain trust, MCP server, and worktree state after migration.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
cli, operating-systems
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.