---
title: "Major, minor e patch: por que o acento circunflexo do package.json decide se seu upgrade quebra"
description: "Rodei um upgrade de dependências num projeto e nada quebrou. Não foi sorte: foi o caret do package.json travando as majors. Como o SemVer funciona, o que cada símbolo faz e por que ainda assim eu verifico o build."
published: "2026-07-24"
tags: ["frontend", "produtividade"]
source: "https://marciotoledo.com/blog/semver-major-minor-patch-upgrade-dependencias"
---

# Major, minor e patch: por que o acento circunflexo do package.json decide se seu upgrade quebra

Rodei um `pnpm update` num projeto meu, um portfólio de domínios em React e Vite, e o diff veio gordo: 1557 linhas inseridas e 1610 removidas só no lockfile. Trinta e poucos pacotes com versão nova.

A primeira reação diante de um diff desse tamanho é sempre a mesma: quebrou alguma coisa? Rodei typecheck, testes, build e abri a aplicação no navegador. Passou tudo. Zero erro de console.

Não foi sorte. Olhando de novo os números das versões, dava pra prever o resultado antes de rodar qualquer comando: nenhum pacote tinha subido de major. E é justamente isso que o `^` no `package.json` garante, silenciosamente, desde o dia em que você instalou cada dependência.

## O que é SemVer, rapidamente

SemVer (Semantic Versioning) é a convenção que dá significado aos três números de uma versão. O formato é `MAJOR.MINOR.PATCH`, e cada posição comunica uma coisa diferente pra quem vai atualizar.

Pegando o TypeScript do meu upgrade, que foi de `5.8.3` pra `5.9.3`:

- **PATCH** (`5.9.2` → `5.9.3`): correção de bug. Nada de novo, nada removido. Só conserta o que estava errado.
- **MINOR** (`5.8.x` → `5.9.0`): funcionalidade nova, mas retrocompatível. Ganhou recurso, e o código que você já tinha continua funcionando igual.
- **MAJOR** (`5.x` → `6.0.0`): breaking change. Removeram uma função, mudaram o comportamento de um parâmetro, inverteram um default. Aqui seu código pode quebrar.

A regra prática cabe numa linha: **só o primeiro número quebra seu código.**

## Onde o caret entra na história

Esse é o pedaço que faltava pra eu entender por que o upgrade foi tranquilo. Abre qualquer `package.json` e olha o símbolo antes do número:

```json
"typescript": "^5.9.3"
```

Aquele acento circunflexo é o *caret*. Ele significa: aceite qualquer versão nova, desde que o primeiro número continue sendo 5.

- Notação
- Aceita
- Não aceita
- Quando usar
---
- `^5.9.3` (caret)
- `5.9.4`, `5.10.0`, `5.99.0`
- `6.0.0`
- Default do npm/pnpm. Pega correção e recurso novo, trava breaking change.
---
- `~5.9.3` (tilde)
- `5.9.4`, `5.9.9`
- `5.10.0`
- Mais conservador: só correção de bug, sem recurso novo.
---
- `5.9.3` (exato)
- só `5.9.3`
- todo o resto
- Quando você precisa de reprodutibilidade absoluta.

Todas as dependências do meu projeto usam caret. Por isso o `pnpm update` pegou tudo que havia de mais novo **dentro** dos ranges e parou nas fronteiras de major. Não precisei configurar nada: a proteção já estava escrita no arquivo.

Foi o que aconteceu na prática:

- Pacote
- Antes
- Depois
- Tipo
---
- `typescript`
- 5.8.3
- 5.9.3
- minor
---
- `eslint`
- 9.32.0
- 9.39.5
- minor
---
- `wrangler`
- 4.93.0
- 4.114.0
- minor
---
- `react-hook-form`
- 7.61.1
- 7.82.0
- minor
---
- `vite`
- 5.4.19
- 5.4.21
- patch
---
- `react`
- 18.3.1
- 18.3.1
- inalterado

Só minor e patch. A promessa do SemVer é exatamente que isso não quebre, e o build confirmou que a promessa se cumpriu.

## A pegadinha das versões 0.x

Aqui tem uma exceção que pega muita gente, e eu tenho três casos dela no mesmo projeto: `lucide-react` em `^0.462.0`, `next-themes` em `^0.3.0`, `vaul` em `^0.9.9`.

Antes do 1.0, o projeto é considerado instável pela própria convenção. E o caret muda de comportamento: em `0.x`, ele trava no **segundo** número. `^0.3.0` aceita `0.3.7`, mas não sobe pra `0.4.0`.

Faz sentido quando você pensa: num projeto pré-1.0, o segundo número é que faz o papel de major. A biblioteca ainda está se achando, e quebrar API entre `0.3` e `0.4` é esperado. O caret é mais restrito justamente porque a promessa de estabilidade ainda não foi feita.

## Por que eu ainda verifico

Poderia terminar o post aqui, com a conclusão confortável de que o caret resolve. Mas seria desonesto.

**SemVer é uma convenção, não uma garantia técnica.** Nada no npm impede um mantenedor de quebrar algo num minor por descuido. É raro em pacote maduro, mas acontece. O número é uma promessa humana, não uma verificação automática.

Por isso, mesmo com o diff dizendo "só minor e patch", rodei:

1. **`pnpm install --frozen-lockfile`**, pra confirmar que o lockfile bate com o `package.json`. Se não bater, o CI vai falhar depois e você descobre no pior momento.
1. **Typecheck** (`tsc --noEmit`), que é o teste que pega mudança de assinatura de tipo sem custo nenhum.
1. **Build de produção**, porque compilar é diferente de checar tipo.
1. **A aplicação aberta no navegador**, porque build passar não significa que a tela renderiza.

Esse último ponto foi o que mais me rendeu no dia. O projeto tem exatamente um teste unitário, e ele é trivial: não valida nada da aplicação. Se eu tivesse olhado só pro `pnpm test` verde, teria uma falsa sensação de cobertura. Quem me deu confiança de verdade foi abrir as duas telas e ver 0 erro no console.

## O truque pra separar o que é seu do que já estava lá

O lint falhou depois do upgrade: 3 erros. A pergunta óbvia é se o upgrade causou aquilo.

Em vez de adivinhar, criei um worktree do Git no commit anterior, instalei as dependências antigas e rodei o lint lá:

```bash
git worktree add --detach /tmp/pre-upgrade HEAD
cd /tmp/pre-upgrade && pnpm install --frozen-lockfile
pnpm lint
```

Resultado idêntico: mesmos 3 erros, mesmos arquivos. Os problemas eram pré-existentes, em código gerado que o upgrade nem tocou.

Vale como técnica geral: **worktree é a forma barata de comparar comportamento entre dois commits sem mexer no seu working tree.** Você não precisa dar stash, nem trocar de branch, nem arriscar perder o que estava fazendo. Cria, testa, remove.

> **TIP**
>
> Se você está começando um projeto agora, mantenha o caret e rode `pnpm update` com frequência (mensal já resolve). Upgrade pequeno e constante é trivial de verificar. Ficar dois anos sem atualizar transforma qualquer mexida numa migração de major com changelog de 40 páginas.

## E quando você quiser subir uma major?

Aí o processo é outro, e o caret não te ajuda mais: ele foi feito exatamente pra impedir isso.

Subir de React 18 pra 19, por exemplo, exige editar o `package.json` na mão ou usar um comando explícito (`pnpm update --latest`). E o método muda:

1. **Uma major por vez.** Se subir cinco pacotes juntos e a tela ficar branca, você não sabe qual foi.
1. **Ler o changelog antes**, não depois. Projeto sério publica guia de migração, e ele costuma listar exatamente o que quebrou.
1. **Commit separado.** Major não entra no mesmo commit que patch, porque o `git revert` precisa ser cirúrgico.

Se você usa Claude Code ou outro agente pra ajudar nesse tipo de tarefa, vale ter o processo escrito no [CLAUDE.md do projeto](/blog/claude-md-contexto-projeto-claude-code): "nunca suba major sem perguntar" é o tipo de regra que evita um diff surpresa.

## Resumo

**Faça isso:**

- Mantenha o caret (`^`) nas dependências. É o default e é bom default.
- Rode `pnpm update` com frequência: upgrades pequenos são fáceis de verificar.
- Verifique com typecheck e build, não só com a suíte de testes (principalmente se ela for magra).
- Use worktree pra provar se um erro é novo ou pré-existente.

**Evite isso:**

- Confiar que "só minor" significa "impossível quebrar". SemVer é promessa, não garantia.
- Subir várias majors no mesmo commit.
- Esquecer que `0.x` segue outra regra: ali o segundo número é que manda.

O resumo do resumo: o caret já te protege da categoria mais perigosa de atualização. O que sobra pra você é verificar, e verificar é barato quando o upgrade é pequeno.

> **NOTE**
>
> Sim, dá pra argumentar que travar tudo em versão exata é mais seguro. E é, no sentido estrito. Mas o custo é acumular anos de defasagem até o dia em que uma CVE te obriga a atualizar tudo de uma vez, sem tempo. Prefiro pagar em parcelas pequenas.

## Referências

- Tom Preston-Werner. [Semantic Versioning 2.0.0](https://semver.org/lang/pt-BR/)
- npm Docs. [About semantic versioning](https://docs.npmjs.com/about-semantic-versioning)
- pnpm Docs. [pnpm update](https://pnpm.io/cli/update)
- Git Docs. [git-worktree](https://git-scm.com/docs/git-worktree)
