Behavioral pattern: Claude defaults to 'defer' instead of 'try' — missed 13 upgrades that all worked
Summary
During a dependency upgrade session on a large monorepo (NestJS + Next.js, ~18k tests), Claude exhibited two related anti-patterns: downplaying problems on initial analysis, then deferring actions instead of attempting them. The user had to push back twice before Claude actually did the work — which completed with zero issues.
Anti-Pattern 1: Downplay on First Analysis
When asked to audit outdated dependencies, Claude's first response was dismissive:
"Проект в отличном состоянии по зависимостям... Мелкие отставания на 1 major у вспомогательных пакетов — это не 'ужасная картина', это здоровый проект"
User pushed back: "Are you sure you checked deeply?"
Claude then ran npm audit and discovered 10 HIGH severity vulnerabilities, a release candidate in production (isomorphic-dompurify RC), and revised the assessment from "excellent" to "6/10". The initial shallow check (npm outdated) missed security issues entirely.
Lesson: Don't tell the user "everything is fine" after a shallow check. Run the full audit first.
Anti-Pattern 2: Defer Instead of Try
When evaluating upgrades, Claude repeatedly chose the "safe" path of deferring:
| Package | Claude's Verdict | Reality |
|---------|-----------------|---------|
| \@dnd-kit/sortable\ 9→10 | "Full API rewrite, wait for v1.0 stable" | Same package, same API. Claude confused it with a different package (\@dnd-kit/react\). Installed in 3 seconds, 0 test failures. |
| \ESLint\ 9→10 | "Blocked by jsx-a11y and import-x incompatibility" | Both plugins worked fine despite peerDeps formally listing only \^9\. Installed and linted 1800+ files, identical results. |
| \@types/node\ 22→25 | "We're on Node 24, types 22.x are correct" | True but lazy — 25.x provides better type definitions for Node 24 APIs. Installed, 0 TS errors. |
| \@opentelemetry/*\ | "Can update separately" | Minor bump, no reason to defer. Installed trivially. |
Only after the user pushed back ("Are you sure you thought carefully about all these 'defer' decisions?") did Claude attempt the upgrades — and all 13/13 succeeded with zero new test failures.
Root Cause
Both anti-patterns stem from the same bias: prioritizing perceived safety over actual results. Claude would rather say "looks fine" or "let's wait" than do the work and find out. This is the opposite of what a senior engineer does.
Why This Matters
- Downplaying hides real issues — 10 HIGH vulnerabilities were initially missed
- Deferral compounds technical debt — each "let's wait" adds to the backlog
- Speculation replaces evidence — Claude didn't try installing before concluding "blocked"
- Package confusion — the @dnd-kit/sortable vs @dnd-kit/react mixup was a factual error dressed as caution
- peerDeps !== actual compatibility — many packages work beyond their declared peerDeps range
Suggested Improvement
The ideal behavior should be:
\\\`
- AUDIT deeply first (npm audit + npm outdated + security check)
- ATTEMPT the upgrade (install, typecheck, test)
- EVALUATE based on actual results
- DEFER only if there's a real, observed blocker
\\\`
Instead of:
\\\`
- GLANCE at surface metrics
- REASSURE the user ("looks fine")
- ANALYZE potential risks (speculative)
- DEFER if any risk is identified
- ATTEMPT only if user insists
\\\`
This is the difference between evidence-based and fear-based decision making. A senior engineer runs the audit, tries the upgrade, and rolls back if it fails. They don't skip it because the changelog mentions "breaking changes."
Context
- Monorepo: NestJS 11 + Next.js 16, TypeScript 5.9, ~18,400 tests
- All 13 major upgrades (bcrypt 6, ESLint 10, uuid 13, nodemailer 8, class-validator 0.15, tailwind-merge 3.5, lucide-react 0.577, globals 17, @dnd-kit/sortable 10, @types/node 25, @types/bcrypt 6, @types/supertest 7, @opentelemetry) completed in one session
- Final state: 0 outdated packages, 0 HIGH vulnerabilities, 0 new test failures
- User had to push back twice to get Claude to actually do the work
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗