
Quando si cambia computer, la prima cosa che si nota con Claude Code è che le conversazioni precedenti non ci sono più.
Non è un bug: le chat di Claude Code non sono salvate nel cloud, ma in file locali dentro il profilo utente.
La buona notizia è che per spostarle basta copiare una cartella.
Quella meno buona è che c’è un dettaglio sui percorsi che, se ignorato, rende le chat invisibili sul nuovo PC.
In questa guida vediamo dove si trovano i file, cosa copiare, come gestire il cambio di percorso e, alla fine, uno script PowerShell che fa tutto in automatico.
DOVE SI TROVANO I FILE DI CLAUDE
Claude Code salva tutto in una cartella nascosta nel profilo utente:
Windows: %USERPROFILE%\.claude\
macOS/Linux: ~/.claude/
Al suo interno c’è la cartella projects\, che contiene una sottocartella per ogni progetto.
Ogni chat è un file .jsonl dentro la sottocartella del progetto a cui appartiene.
Il nome della sottocartella non è casuale: è il percorso assoluto del progetto, con i separatori (e in generale tutti i caratteri non alfanumerici) sostituiti da trattini.
Ad esempio:
C:\Users\raf\Progetti\piva-manager
diventa:
C–Users-raf-Progetti-piva-manager
COSA COPIARE SUL NUOVO PC
Per una migrazione completa servono due elementi.
1. La cartella .claude\, che contiene:
projects\: le chat
settings.json: permessi e impostazioni
CLAUDE.md: le istruzioni globali
commands\, agents\ e skills\: se ne hai creati
2. Il file .claude.json, che si trova nella cartella utente accanto a .claude\, non dentro. Contiene la configurazione dei server MCP e le impostazioni dei singoli progetti. È facile dimenticarlo.
ATTENZIONE: non copiare le credenziali. Sul nuovo PC avvia claude e rifai l’accesso con /login.
IL PUNTO CRITICO: I PERCORSI
Claude Code associa le chat a un progetto tramite il suo percorso assoluto. Quando lanci claude –resume dentro una cartella, Claude Code calcola il nome codificato di quella cartella e cerca le chat nella sottocartella corrispondente di projects\.
Ci sono quindi due scenari.
Il percorso resta uguale
Stesso nome utente, stessa struttura di cartelle: copi .claude\ e .claude.json e funziona tutto senza fare altro.
Il percorso cambia
È il caso tipico quando sul nuovo PC il nome utente è diverso, ad esempio da C:\Users\raf\… a C:\Users\rchiatto\…, oppure quando sposti i progetti su un altro disco.
In questo caso devi rinominare ogni sottocartella dentro projects\ in base al nuovo percorso:
C–Users-raf-Progetti-app-manager -> C–Users-rchiatto-Progetti-app-manager
Se non lo fai, claude –resume non trova le chat di quel progetto, anche se i file sono lì.
Inoltre i percorsi assoluti compaiono anche dentro i file: nelle chat .jsonl, in .claude.json (dove le impostazioni sono indicizzate per percorso di progetto) e a volte nei permessi di settings.json. Per una migrazione pulita conviene aggiornarli tutti.
CONSIGLIO: il modo più semplice per evitare problemi è usare sul nuovo PC lo stesso nome utente e la stessa struttura di cartelle per i progetti.
Prima di copiare: la pulizia automatica delle sessioni
Di default Claude Code cancella le sessioni più vecchie di 30 giorni. Se vuoi conservarle più a lungo, aggiungi questa riga in settings.json:
|
0
1
|
json
{ "cleanupPeriodDays": 365 }
|
Conviene farlo subito sul vecchio PC, prima della migrazione, così nel frattempo non perdi altre chat.
Riaprire le chat sul nuovo PC
Dopo aver copiato i file, entra nella cartella del progetto e lancia:
claude –resume: per scegliere una chat dall’elenco
claude –continue: per riprendere l’ultima chat
SCRIPT POWERSHELL
Fare tutto a mano va bene con due o tre progetti.
Con molti progetti, o con un cambio di nome utente, rinominare le cartelle e correggere i percorsi nei file diventa lungo e facile da sbagliare.
Per questo ho preparato uno script PowerShell che lavora in due fasi.
Cosa fa in modalità Esporta (vecchio PC)
- Imposta cleanupPeriodDays a 365 in settings.json (configurabile).
- Copia .claude\ escludendo il file delle credenziali e copia .claude.json.
- Salva in un manifest.json il percorso del profilo utente di origine.
- Crea un archivio .zip pronto da trasferire.
Cosa fa in modalità Importa (nuovo PC)
- Estrae l’archivio in una cartella temporanea.
- Rinomina le sottocartelle di projects\ dal vecchio al nuovo percorso.
- Aggiorna i percorsi dentro le chat .jsonl, .claude.json e settings.json.
- Fa un backup della configurazione già presente (.claude.backup-<data> e .claude.json.backup-<data>).
- Unisce i file nel profilo utente: se hai già fatto il login sul nuovo PC, resta valido.
- Imposta cleanupPeriodDays anche sul nuovo PC.
Come si usa
Sul vecchio PC:
Da una powershell con diritti amministrativi eseguire il comando:
|
0 |
.\Migra-ClaudeCode.ps1 -Modalita Esporta -Archivio D:\claude-backup.zip
|
Copia lo zip sul nuovo PC e lancia:
|
0 |
.\Migra-ClaudeCode.ps1 -Modalita Importa -Archivio D:\claude-backup.zip
|
Il cambio di profilo utente (ad esempio da C:\Users\raf a C:\Users\rchiatto) viene rilevato in automatico.
Se alcuni progetti stavano fuori dal profilo, o li hai spostati, aggiungi una mappatura vecchio → nuovo percorso con il comando:
|
0 |
.\Migra-ClaudeCode.ps1 -Modalita Importa -Archivio D:\claude-backup.zip -MappaPercorsi @{ 'D:\Progetti' = 'C:\Dev' }
|
Parametri Disponibili
- Modalita: Esporta oppure Importa
- Archivio: Percorso dello zip da creare o da leggere
- MappaPercorsi: Mappature aggiuntive vecchio → nuovo percorso (solo Importa)
- VecchioProfilo: Forza il profilo di origine invece di leggerlo dal manifest (solo Importa)
- NonAggiornareContenuti: Rinomina solo le cartelle, senza modificare i percorsi nei file (solo Importa)
- GiorniConservazione: Valore di cleanupPeriodDays (default 365, 0 per non modificarlo)
Note Pratiche
Chiudi tutte le sessioni di Claude Code prima di lanciare lo script. Se ne trova una aperta, ti chiede conferma.
Se Windows blocca l’esecuzione degli script, avvialo così:
|
0 |
powershell -ExecutionPolicy Bypass -File .\Migra-ClaudeCode.ps1 -Modalita Importa -Archivio D:\claude-backup.zip
|
Al termine, avvia claude e fai /login, poi entra nella cartella del progetto e lancia claude –resume.
Lo script è compatibile con Windows PowerShell 5.1 e con PowerShell 7.
CONCLUSIONI
Spostare le chat di Claude Code su un nuovo PC è semplice se si ricordano tre cose: copiare sia .claude\ sia .claude.json, rifare il login invece di copiare le credenziali e, se il percorso dei progetti cambia, rinominare le cartelle in projects\.
Lo script qui sotto si occupa di tutti e tre i punti e fa un backup prima di toccare qualsiasi cosa.
SCRIPT
Salva il codice seguente come Migra-ClaudeCode.ps1.
|
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
<#
.SYNOPSIS
Migra chat, impostazioni e configurazione di Claude Code da un PC Windows a un altro.
.DESCRIPTION
Modalita Esporta (vecchio PC):
- copia %USERPROFILE%\.claude\ (escluse le credenziali) e %USERPROFILE%\.claude.json
- salva il percorso del profilo di origine in un manifest
- crea un archivio .zip
Modalita Importa (nuovo PC):
- estrae l'archivio in una cartella temporanea
- rinomina le sottocartelle di projects\ in base al nuovo percorso
- aggiorna i percorsi dentro i file .jsonl, .claude.json e settings.json
- fa un backup di quanto gia presente e poi unisce i file in %USERPROFILE%
- imposta cleanupPeriodDays in settings.json
.PARAMETER Modalita
Esporta oppure Importa.
.PARAMETER Archivio
Percorso del file .zip da creare (Esporta) o da leggere (Importa).
.PARAMETER MappaPercorsi
Solo Importa. Mappature aggiuntive vecchio -> nuovo percorso, per progetti che stavano
fuori dal profilo utente o che hai spostato. Es: @{ 'D:\Progetti' = 'C:\Dev' }
Il cambio del profilo utente (C:\Users\vecchio -> C:\Users\nuovo) e' gia gestito in automatico.
.PARAMETER VecchioProfilo
Solo Importa. Forza il percorso del profilo di origine (di default lo legge dal manifest).
.PARAMETER NonAggiornareContenuti
Solo Importa. Rinomina solo le cartelle, senza modificare i percorsi dentro i file.
.PARAMETER GiorniConservazione
Valore di cleanupPeriodDays da scrivere in settings.json (0 = non toccare). Default 365.
In Esporta viene applicato anche sul vecchio PC, cosi le chat non vengono cancellate nel frattempo.
.EXAMPLE
# Sul vecchio PC
.\Migra-ClaudeCode.ps1 -Modalita Esporta -Archivio D:\claude-backup.zip
.EXAMPLE
# Sul nuovo PC
.\Migra-ClaudeCode.ps1 -Modalita Importa -Archivio D:\claude-backup.zip
.EXAMPLE
# Sul nuovo PC, con progetti spostati da D:\Progetti a C:\Dev
.\Migra-ClaudeCode.ps1 -Modalita Importa -Archivio D:\claude-backup.zip -MappaPercorsi @{ 'D:\Progetti' = 'C:\Dev' }
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][ValidateSet('Esporta', 'Importa')][string]$Modalita,
[Parameter(Mandatory)][string]$Archivio,
[hashtable]$MappaPercorsi = @{},
[string]$VecchioProfilo,
[switch]$NonAggiornareContenuti,
[int]$GiorniConservazione = 365
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.IO.Compression.FileSystem
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$Profilo = $env:USERPROFILE
$DirClaude = Join-Path $Profilo '.claude'
$FileClaudeJs = Join-Path $Profilo '.claude.json'
$Archivio = [IO.Path]::GetFullPath($Archivio)
# --- Funzioni ---------------------------------------------------------------
function Get-NomeCartellaProgetto([string]$Percorso) {
# Claude Code sostituisce ogni carattere non alfanumerico con '-'
return ($Percorso.TrimEnd('\', '/') -replace '[^a-zA-Z0-9]', '-')
}
function Invoke-Robocopy([string]$Da, [string]$A, [string[]]$Extra = @()) {
$arg = @($Da, $A, '/E', '/R:1', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP') + $Extra
& robocopy @arg | Out-Null
if ($LASTEXITCODE -ge 8) { throw "robocopy fallito ($LASTEXITCODE): $Da -> $A" }
}
function Set-Conservazione([string]$Cartella, [int]$Giorni) {
if ($Giorni -le 0) { return }
if (-not (Test-Path $Cartella)) { New-Item -ItemType Directory -Path $Cartella | Out-Null }
$file = Join-Path $Cartella 'settings.json'
if (Test-Path $file) {
$cfg = Get-Content $file -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -eq $cfg) { $cfg = New-Object PSObject }
} else {
$cfg = New-Object PSObject
}
$attuale = $cfg.cleanupPeriodDays
if ($null -eq $attuale -or [int]$attuale -lt $Giorni) {
$cfg | Add-Member -NotePropertyName cleanupPeriodDays -NotePropertyValue $Giorni -Force
[IO.File]::WriteAllText($file, ($cfg | ConvertTo-Json -Depth 50), $Utf8NoBom)
Write-Host " cleanupPeriodDays impostato a $Giorni in $file"
} else {
Write-Host " cleanupPeriodDays gia a $attuale, non modificato"
}
}
function Update-PercorsiNelTesto([string]$File, $Mappe) {
$testo = [IO.File]::ReadAllText($File, [Text.Encoding]::UTF8)
$orig = $testo
foreach ($m in $Mappe) {
# Forma JSON con backslash raddoppiati (C:\\Users\\raf) e forma con slash (C:/Users/raf)
$forme = @(
@{ Da = $m.Vecchio.Replace('\', '\\'); A = $m.Nuovo.Replace('\', '\\') },
@{ Da = $m.Vecchio.Replace('\', '/'); A = $m.Nuovo.Replace('\', '/') }
)
foreach ($f in $forme) {
$pattern = [regex]::Escape($f.Da) + '(?![A-Za-z0-9_.\-])'
$testo = [regex]::Replace($testo, $pattern, $f.A.Replace('$', '$$'), 'IgnoreCase')
}
}
if ($testo -cne $orig) {
[IO.File]::WriteAllText($File, $testo, $Utf8NoBom)
return $true
}
return $false
}
# --- Controllo preliminare --------------------------------------------------
if (Get-Process -Name 'claude' -ErrorAction SilentlyContinue) {
Write-Warning 'Claude Code risulta in esecuzione. Chiudi tutte le sessioni prima di continuare.'
if ((Read-Host 'Continuare comunque? (s/N)') -ne 's') { return }
}
$Temp = Join-Path $env:TEMP ('claude-migrazione-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
New-Item -ItemType Directory -Path $Temp | Out-Null
try {
# =======================================================================
if ($Modalita -eq 'Esporta') {
# =======================================================================
if (-not (Test-Path $DirClaude)) { throw "Cartella non trovata: $DirClaude" }
Write-Host '1/4 Impostazione conservazione chat sul PC attuale...'
Set-Conservazione -Cartella $DirClaude -Giorni $GiorniConservazione
Write-Host '2/4 Copia di .claude (senza credenziali)...'
Invoke-Robocopy $DirClaude (Join-Path $Temp '.claude') @('/XF', '.credentials.json')
if (Test-Path $FileClaudeJs) { Copy-Item $FileClaudeJs (Join-Path $Temp '.claude.json') }
else { Write-Warning ".claude.json non trovato in $Profilo" }
Write-Host '3/4 Scrittura manifest...'
@{
ProfiloOrigine = $Profilo
Computer = $env:COMPUTERNAME
Utente = $env:USERNAME
Data = (Get-Date).ToString('s')
} | ConvertTo-Json | Set-Content (Join-Path $Temp 'manifest.json') -Encoding UTF8
Write-Host '4/4 Creazione archivio...'
if (Test-Path $Archivio) { Remove-Item $Archivio -Force }
$dirArchivio = Split-Path $Archivio -Parent
if (-not (Test-Path $dirArchivio)) { New-Item -ItemType Directory -Path $dirArchivio | Out-Null }
[IO.Compression.ZipFile]::CreateFromDirectory($Temp, $Archivio)
$nChat = @(Get-ChildItem (Join-Path $Temp '.claude\projects') -Recurse -Filter *.jsonl -ErrorAction SilentlyContinue).Count
$mb = [math]::Round((Get-Item $Archivio).Length / 1MB, 1)
Write-Host ''
Write-Host "Fatto. Archivio: $Archivio ($mb MB, $nChat chat)." -ForegroundColor Green
Write-Host 'Copialo sul nuovo PC e lancia lo script in modalita Importa.'
}
# =======================================================================
else {
# =======================================================================
if (-not (Test-Path $Archivio)) { throw "Archivio non trovato: $Archivio" }
Write-Host '1/6 Estrazione archivio...'
[IO.Compression.ZipFile]::ExtractToDirectory($Archivio, $Temp)
$TmpClaude = Join-Path $Temp '.claude'
$TmpClaudeJs = Join-Path $Temp '.claude.json'
$TmpProjects = Join-Path $TmpClaude 'projects'
if (-not $VecchioProfilo) {
$man = Join-Path $Temp 'manifest.json'
if (-not (Test-Path $man)) { throw 'manifest.json mancante: specifica -VecchioProfilo' }
$VecchioProfilo = (Get-Content $man -Raw | ConvertFrom-Json).ProfiloOrigine
}
Write-Host " Profilo origine: $VecchioProfilo"
Write-Host " Profilo destinazione: $Profilo"
# Mappature, dalla piu specifica alla piu generica
$Mappe = @()
foreach ($k in $MappaPercorsi.Keys) {
$Mappe += [pscustomobject]@{ Vecchio = $k.TrimEnd('\'); Nuovo = ([string]$MappaPercorsi[$k]).TrimEnd('\') }
}
if ($VecchioProfilo.TrimEnd('\') -ne $Profilo.TrimEnd('\')) {
$Mappe += [pscustomobject]@{ Vecchio = $VecchioProfilo.TrimEnd('\'); Nuovo = $Profilo.TrimEnd('\') }
}
$Mappe = @($Mappe | Sort-Object { $_.Vecchio.Length } -Descending)
foreach ($m in $Mappe) { Write-Host " Mappa: $($m.Vecchio) -> $($m.Nuovo)" }
Write-Host '2/6 Rinomina cartelle dei progetti...'
$rinominate = 0
if ($Mappe.Count -gt 0 -and (Test-Path $TmpProjects)) {
foreach ($dir in Get-ChildItem $TmpProjects -Directory) {
$nome = $dir.Name
$nuovoNome = $null
foreach ($m in $Mappe) {
$prefOld = Get-NomeCartellaProgetto $m.Vecchio
if ($nome.StartsWith($prefOld, [StringComparison]::OrdinalIgnoreCase) -and
($nome.Length -eq $prefOld.Length -or $nome[$prefOld.Length] -eq '-')) {
$nuovoNome = (Get-NomeCartellaProgetto $m.Nuovo) + $nome.Substring($prefOld.Length)
break
}
}
if (-not $nuovoNome -or $nuovoNome -ceq $nome) { continue }
$dest = Join-Path $TmpProjects $nuovoNome
if ($nuovoNome -eq $nome) {
# Cambia solo maiuscole/minuscole: passaggio tramite nome temporaneo
$tmpName = $nome + '__tmp'
Rename-Item $dir.FullName $tmpName
Rename-Item (Join-Path $TmpProjects $tmpName) $nuovoNome
} elseif (Test-Path $dest) {
Invoke-Robocopy $dir.FullName $dest @('/MOVE')
if (Test-Path $dir.FullName) { Remove-Item $dir.FullName -Recurse -Force }
} else {
Rename-Item $dir.FullName $nuovoNome
}
Write-Host " $nome -> $nuovoNome"
$rinominate++
}
}
Write-Host " Cartelle rinominate: $rinominate"
Write-Host '3/6 Aggiornamento percorsi nei file...'
$aggiornati = 0
if ($Mappe.Count -gt 0 -and -not $NonAggiornareContenuti) {
$file = @()
if (Test-Path $TmpProjects) { $file += Get-ChildItem $TmpProjects -Recurse -File -Filter *.jsonl }
if (Test-Path $TmpClaudeJs) { $file += Get-Item $TmpClaudeJs }
$settings = Join-Path $TmpClaude 'settings.json'
if (Test-Path $settings) { $file += Get-Item $settings }
$i = 0
foreach ($f in $file) {
$i++
Write-Progress -Activity 'Aggiornamento percorsi' -Status $f.Name -PercentComplete (100 * $i / $file.Count)
if (Update-PercorsiNelTesto $f.FullName $Mappe) { $aggiornati++ }
}
Write-Progress -Activity 'Aggiornamento percorsi' -Completed
}
Write-Host " File modificati: $aggiornati"
Write-Host '4/6 Backup della configurazione esistente...'
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
if (Test-Path $DirClaude) {
$bk = "$DirClaude.backup-$stamp"
Invoke-Robocopy $DirClaude $bk
Write-Host " $bk"
}
if (Test-Path $FileClaudeJs) {
Copy-Item $FileClaudeJs "$FileClaudeJs.backup-$stamp"
Write-Host " $FileClaudeJs.backup-$stamp"
}
Write-Host '5/6 Copia nel profilo utente...'
Invoke-Robocopy $TmpClaude $DirClaude # unione: le credenziali gia presenti restano
if (Test-Path $TmpClaudeJs) { Copy-Item $TmpClaudeJs $FileClaudeJs -Force }
Write-Host '6/6 Impostazione conservazione chat...'
Set-Conservazione -Cartella $DirClaude -Giorni $GiorniConservazione
Write-Host ''
Write-Host 'Importazione completata.' -ForegroundColor Green
Write-Host 'Prossimi passi:'
Write-Host ' 1. claude e poi /login (le credenziali non vengono migrate)'
Write-Host ' 2. cd nella cartella del progetto, poi claude --resume'
Write-Host ''
Write-Host 'Cartelle progetto presenti ora:'
Get-ChildItem (Join-Path $DirClaude 'projects') -Directory -ErrorAction SilentlyContinue |
ForEach-Object { Write-Host " $($_.Name)" }
}
}
finally {
Remove-Item $Temp -Recurse -Force -ErrorAction SilentlyContinue
}
|

0 Commenti