🚀 Overview & 5 Migration Types
Cada vez que Claude Code inicia, pode silenciosamente corrigir settings stale, remapear aliases de modelo, mover campos entre arquivos ou re-surfacear dialogos UI. Migracoes sao funcoes idempotentes one-shot dentro de runMigrations() em main.tsx.
~/.claude.json para settings.json⚙️ The Runner: runMigrations()
Todas as migracoes sincronas sao encapsuladas em uma unica funcao. Chamada durante o hook preAction do Commander, apos config carregado e antes do REPL iniciar.
const CURRENT_MIGRATION_VERSION = 11;
function runMigrations(): void {
if (getGlobalConfig().migrationVersion !== CURRENT_MIGRATION_VERSION) {
migrateAutoUpdatesToSettings();
migrateBypassPermissionsAcceptedToSettings();
// ... 9 more sync migrations ...
saveGlobalConfig(prev => ({...prev, migrationVersion: CURRENT_MIGRATION_VERSION}));
}
// Async migration -- fire-and-forget
migrateChangelogFromConfig().catch(() => {});
}
Version gate: migrationVersion em ~/.claude.json. Uma vez igual a CURRENT_MIGRATION_VERSION, o bloco inteiro e pulado, evitando 11 ciclos redundantes de config save.
🔏 Two Idempotency Patterns
Pattern A: Completion Flag
Usado quando "ja feito" nao e auto-evidente. Boolean/timestamp em ~/.claude.json.
if (config.sonnet1m45MigrationComplete) return;
// ... do migration ...
saveGlobalConfig({...c, sonnet1m45MigrationComplete: true})
Pattern B: Self-Idempotent
Usado quando o dado fala por si. Se ja migrado, a checagem retorna false.
const model = getSettingsForSource('userSettings')?.model;
if (model !== 'claude-opus-4-20250514') return;
updateSettingsForSource('userSettings', { model: 'opus' })
🔒 Settings Layer Discipline
Migracoes so tocam userSettings (e ocasionalmente localSettings). Nunca leem de merged settings.
Por que? Se uma migracao lesse de settings merged, poderia ver um setting project-scoped e "ajudadamente" escrever esse valor no global userSettings, subitamente tornando uma preferencia por-projeto o novo default em todos os projetos.
📊 Analytics Instrumentation
A maioria das migracoes chama logEvent() para registrar o que aconteceu. Isso permite ao time saber quando uma migracao ainda esta sendo aplicada ou quando e seguro remover o codigo.
Regra critica: Migracoes NUNCA devem fazer throw. Erros sao capturados, logados e silenciosamente engolidos para nao quebrar startup.
📝 Adding a New Migration
- Crie
src/migrations/myNewMigration.tscom funcao exportada unica - Escolha estrategia de idempotencia: completion flag ou self-idempotent
- So leia/escreva em
userSettings(oulocalSettingspara dados project-scoped) - Chame
logEvent('tengu_my_migration_name', {...}) - Encapsule o body em try/catch -- migracoes nunca devem fazer throw
- Importe em
main.tsxdentro do blocoif (migrationVersion !== CURRENT_MIGRATION_VERSION) - Incremente CURRENT_MIGRATION_VERSION para que usuarios existentes re-executem o set