Turbopack Rust panic: dissecting and fixing inner_of_uppers_lost_follower in next.js 16
Back to blog

Turbopack Rust panic: dissecting and fixing inner_of_uppers_lost_follower in next.js 16

6/7/2026 · 6 min · Development

Rust panic on dev server startup#

Environment: Next.js 16.0.0, Turbopack enabled, pnpm (versions 8.x or 9.x recommended) as package manager, Linux. Node.js Prerequisites: Next.js 16 requires Node.js 18.17+ (with Node.js 20 LTS highly recommended). Check your environment version by running:

node --version
pnpm --version

Startup command:

NODE_OPTIONS='--max-old-space-size=4096' next dev -H 0.0.0.0

The development server aborted during the initial bundling phase with the following fatal stderr output:

thread 'tokio-runtime-worker' (862) panicked at
/build/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs:1420:17:
inner_of_uppers_lost_follower is not able to remove follower TaskId 17
(ProjectContainer::entrypoints) from TaskId 16 (EntrypointsOperation::new)
as they don't exist as upper or follower edges
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Error [TurbopackInternalError]: inner_of_uppers_lost_follower is not able to
remove follower TaskId 17 (ProjectContainer::entrypoints) from TaskId 16
(EntrypointsOperation::new) as they don't exist as upper or follower edges
at Object.resolveEntrypoints ...
at async DevServer.prepare ...

The error did not originate in the application code: no .ts, .tsx, or .js file from the project was mentioned as the source. The panic occurred inside the Turbopack runtime itself, in Rust code.

Panic anatomy: what the error actually says#

Decoding the stack trace#

Turbopack is written in Rust and uses an incremental computation system called turbo-tasks. This system maintains a directed dependency graph where each node represents a "task", the unit of work that can be a file compilation operation, import resolution, bundle output generation, etc.

The graph data model is:

flowchart TD subgraph turbo-tasks["turbo-tasks: Dependency Graph"] T17["TaskId 17\nProjectContainer::entrypoints"] T16["TaskId 16\nEntrypointsOperation::new"] T15["TaskId 15\nModuleResolver"] T14["TaskId 14\nChunkBuilder"] T17 -->|"follower edge\n(invalidate on change)"| T16 T15 -->|"follower edge"| T17 T16 -->|"upper edge"| T14 end PANIC["💥 PANIC\naggregation_update.rs:1420\nfollower edge not found"] T16 -.->|"remove() returns false"| PANIC style PANIC fill:#7f1d1d,color:#fca5a5 style T16 fill:#1e3a5f,color:#93c5fd style T17 fill:#1e3a5f,color:#93c5fd

A "follower edge" means: "when TaskId 17 (project entrypoints) completes, notify TaskId 16 (entrypoints operation)". This is the invalidation propagation mechanic that makes HMR so fast in Turbopack.

What "inner_of_uppers_lost_follower" means#

The inner_of_uppers_lost_follower function in aggregation_update.rs:1420 is part of the dependency aggregation algorithm, responsible for consolidating and propagating updates through the graph when a file changes.

The panic occurs at the line:

// aggregation_update.rs:1420
assert!(
inner_task.followers.remove(&upper_task_id),
"inner_of_uppers_lost_follower is not able to remove follower TaskId {} ({:?}) \
 from TaskId {} ({:?}) as they don't exist as upper or follower edges",
upper_task_id, ..., inner_task_id, ...
);

Operational translation: The Turbopack engine attempted to remove a dependency edge between two graph nodes (TaskId 17 → TaskId 16), but this edge did not exist in the current graph state. The internal invariant was violated - the graph is in an inconsistent state - and Rust, which does not do silent failures on assertions, caused a panic.

Why does the state become inconsistent?#

Corruptions in the turbo-tasks-backend occur primarily in two scenarios:

  1. Residual cache from a previous version: The .next folder contains graph state serialization from a different Turbopack version. When updating Next.js or pnpm and reusing the old cache, the deserialized graph may contain references to TaskIds that no longer exist in the new version.
  1. Concurrency bug in tokio-runtime-worker: Multiple threads of the Rust async runtime (tokio) operating on the graph simultaneously may, under race conditions, produce inconsistent state. The thread 'tokio-runtime-worker' (862) prefix in the panic indicates the error occurred in a worker thread of the async thread pool.

1.1 verifying cache corruption#

Before executing aggressive clean operations, you can inspect the presence and size of the cache on disk, and verify if file system locks or daemon processes are interfering with the cache validation:

# Verify structured cache folders
ls -la .next/cache/

# Analyze disk space usage of the cache directory
du -sh .next/cache/

# Check if there are active lock files
ls -la .next/cache/*.lock 2>/dev/null

# Search for active or ghost Turbopack dev server processes
ps aux | grep -i turbopack

1.2 safe cleanup via next clean#

Deleting folders manually via rm -rf is immediate, but Next.js offers a safer native alternative: next clean. The next clean utility is preferred because it only deletes cache artifacts generated by the framework itself, protecting customized sub-files you might have manually mapped inside .next:

# Run via npx
npx next clean

# Or using pnpm
pnpm next clean

1.3 multi-platform file system deletion#

If you need to completely discard the directory, run the specific command for your host operating system:

# macOS and Linux
rm -rf .next

# Windows (PowerShell with Admin privileges)
Remove-Item -Recurse -Force .next

# Windows (Command Prompt - CMD)
rmdir /s /q .next

# Safe Unix directory content deletion alternative
find .next -type f -delete && find .next -type d -empty -delete

What this procedure removes:

After the purge, Turbopack builds the graph from a clean slate:

NODE_OPTIONS='--max-old-space-size=4096' pnpm dev

Verifying the resolution: The dev server should compile without spawning any TurbopackInternalError output. Expected output sequence:

  ▲ Next.js 16.0.0 (Turbopack)
  - Local:        http://localhost:3000
  - Network:      http://0.0.0.0:3000

 ✓ Starting...
 ✓ Ready in 2.4s

1.4 pnpm compatibility#

Next.js 16 recommends recent releases of the pnpm package manager (8.x or 9.x series). Verify your binary versions or perform a global upgrade to prevent lockfile conflicts and nested dependency resolution issues:

# Verify current version
pnpm --version

# Upgrade globally to stable 9.x
npm install -g pnpm@9

1.5 Docker and CI/CD cache mitigations#

Stale build caches from previous runs or host mounts are the leading cause of compiler panics in CI/CD pipelines and Docker container packaging.

Dockerfile (Hardened Builds):

# Purge old Next cache before running installation and build steps
RUN rm -rf .next || true
# Alternatively, use the official CLI
RUN npx next clean || true
RUN pnpm build

GitHub Actions Workflow (ci.yml):

# Step to purge cache before compiling Next.js assets
- name: Clean Next.js cache
  run: rm -rf .next || npx next clean

- name: Start build
  run: pnpm build

1.6 IDE and file watcher exclusions#

IDEs like VS Code or the JetBrains family (IntelliJ, WebStorm) execute recursive file system watchers and language service indexes. If these tools scan the .next directory while Turbopack is compiling/updating caching structures, file-access locks can disrupt Turbopack's background operations, triggering panic crashes.

Solution: Prevent IDE watch indexing by excluding .next in .vscode/settings.json:

{
  "files.watcherExclude": {
    "**/.next/**": true,
    "**/node_modules/**": true
  }
}

Why does the next.js cache become corrupted?#

# Common causes that produce the panic after reinstallation:
pnpm update next           → New Turbopack version with different cache schema
rm -rf node_modules && bun install  → Changes runtime, serialized graph incompatible
git pull (with tsconfig.json, next.config.js changes)  → Incomplete invalidation

To prevent development workflow delays, add clean scripts to your package.json:

{
  "scripts": {
    "dev": "next dev --turbo",
    "clean": "next clean",
    "dev:clean": "next clean && next dev --turbo",
    "build:clean": "next clean && next build"
  }
}

Solution 2 - webpack fallback (development continuity)#

If the panic persists after multiple cache clears (indicating a version-specific bug), the immediate exit is removing Turbopack from the equation:

package.json - before:

{
  "scripts": {
"dev": "next dev --turbo"
  }
}

package.json - after:

{
  "scripts": {
"dev": "next dev"
  }
}

Or, if Turbopack was enabled via next.config.js:

// next.config.js - BEFORE (Turbopack active)
const nextConfig = {
  experimental: {
turbo: {
  rules: { /* ... */ }
}
  }
}

// next.config.js - AFTER (Webpack as default)
const nextConfig = {
  // experimental.turbo removed
}

Fallback trade-offs:

AspectTurbopackWebpack fallback
HMR latency~150ms~800ms - 2.4s
RAM at idle~1.3 GB~2.8 GB
Stability on major release⚠️ Bugs expected initially✅ More stable
Access to Next.js 16 features✅ Full✅ Full

The Webpack fallback does not lose any Next.js 16 features: it simply uses the older compiler. Development continues normally while the Vercel team patches the bug.

Solution 3 - upgrading to canary version#

If the panic is a bug already identified and fixed in Next.js nightly builds, the fastest path to stable Turbopack is the canary version:

# With pnpm
pnpm update next@canary react@canary react-dom@canary

# With bun
bun update next@canary react@canary react-dom@canary

Checking the installed canary version:

pnpm list next
# └─ next 16.0.1-canary.15 (or higher)

When to use canary vs. waiting for stable:

CriterionCanaryWait for Stable
MVP / development project✅ Acceptable✅ Safe
Production project❌ Risky✅ Mandatory
Critical blocking build error✅ Try canary-
Team with debug capacity✅ Canary useful-

Monitoring the Next.js repository for the patch:

# Search for related issues via GitHub CLI (if installed)
gh issue list --repo vercel/next.js --search "inner_of_uppers_lost_follower"

# Or follow the canary CHANGELOG
curl -s https://raw.githubusercontent.com/vercel/next.js/canary/packages/next/CHANGELOG.md | grep -A 5 "turbopack"

3.1 submitting and creating a GitHub issue#

If the panic persists and no similar issue exists, it is critical to report the crash with complete diagnostics. You can draft an issue in your browser or automatically using the GitHub CLI:

# Create a detailed bug report using local system metadata
gh issue create --repo vercel/next.js \
  --title "Turbopack panic: inner_of_uppers_lost_follower in Next.js 16" \
  --body "## Environment Info
- Next.js: \$(pnpm list next 2>/dev/null | grep next | awk '{print \$2}' || echo '16.0.0')
- Node.js: \$(node --version)
- OS: \$(uname -a)
- pnpm: \$(pnpm --version)

## Rust panic backtrace stack
\`\`\`
\$(cat /tmp/turbopack-panic.log 2>/dev/null || echo 'thread tokio-runtime-worker panicked at inner_of_uppers_lost_follower')
\`\`\`

## Steps to reproduce
1. Enable experimental Turbopack routing (\`--turbo\`) in Next.js 16.
2. Fire up the development environment with \`pnpm dev\`.
3. Modify dependencies or reload entrypoint files rapidly during HMR."

# Or open the GitHub issue templates page in your default browser
gh issue create --repo vercel/next.js --web

Enabling backtrace and advanced monitoring#

6.1 Rust backtraces#

If you need to report the bug to the Next.js issue tracker or trace the exact origin of a compiler panic, running dev with RUST_BACKTRACE=1 prints the Rust internal stack trace:

RUST_BACKTRACE=1 NODE_OPTIONS='--max-old-space-size=4096' pnpm dev 2>&1 | tee /tmp/turbopack-panic.log

The output with backtrace includes every Rust call stack frame at the moment of the panic:

stack backtrace:
   0: rust_begin_unwind
         at /rustc/.../library/std/src/panicking.rs:647:5
   1: core::panicking::panic_fmt
         at /rustc/.../library/core/src/panicking.rs:72:14
   2: turbo_tasks_backend::backend::operation::aggregation_update::inner_of_uppers_lost_follower
         at /build/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs:1420:17
   3: turbo_tasks_backend::backend::operation::aggregation_update::AggregationUpdateQueue::process
         at .../aggregation_update.rs:890:13
   4: turbo_tasks_backend::backend::TurboTasksBackend::run_backend_job
         at .../mod.rs:421:9
   ...

6.2 monitoring RAM and memory limits#

Compiling complex Next.js packages under watch mode can leak resources or hit V8 heap allocations thresholds. You can monitor active processes and check limits with:

# Print active Node processes with CPU/Memory footprint details
ps aux | grep node | awk '{print "PID: "$2" | CPU: "$3"% | RAM: "$4"% ("$6" KB) | Cmd: "$11}'

# Monitor Next.js threads and subprocesses in real-time
htop -p $(pgrep -f "next dev" | paste -sd,)

# Launch Node inspector backend to inspect performance profiles
NODE_OPTIONS='--max-old-space-size=4096 --inspect' pnpm dev

6.3 diagnostic script (diagnose-turbopack.sh)#

To simplify triage in local setups, environments, or containers, copy this shell script into diagnose-turbopack.sh:

#!/bin/bash
# diagnose-turbopack.sh - Triages Turbopack environment and cache anomalies
set -euo pipefail

echo "==========================================="
echo "  TURBOPACK / NEXT.JS RUNTIME DIAGNOSTIC"
echo "==========================================="
echo ""

echo "[1] Checking Environment Versions:"
echo "    Node.js: \$(node --version)"
if command -v pnpm >/dev/null 2>&1; then
    echo "    pnpm:    \$(pnpm --version)"
    echo "    Next.js: \$(pnpm list next 2>/dev/null | grep ' next ' | awk '{print \$2}' || echo 'Not listed')"
else
    echo "    pnpm:    Not installed"
    echo "    Next.js: (install pnpm to check package version)"
fi
echo "    Kernel:  \$(uname -s) \$(uname -r)"
echo ""

echo "[2] Inspecting .next Directory:"
if [ -d ".next" ]; then
    echo "    Exists:   Yes"
    echo "    Size:     \$(du -sh .next 2>/dev/null | awk '{print \$1}')"
    echo "    Modified: \$(stat -c %y .next 2>/dev/null || stat -f %Sm .next 2>/dev/null)"
else
    echo "    Exists:   No"
fi
echo ""

echo "[3] Active Next/Turbopack Processes:"
PROCS=\$(ps aux | grep -E "next|turbopack" | grep -v grep || true)
if [ -n "\$PROCS" ]; then
    echo "\$PROCS"
else
    echo "    No processes running."
fi
echo ""

echo "[4] Available RAM Memory:"
if command -v free &>/dev/null; then
    free -h | head -2
else
    echo "    'free' utility not supported/available."
fi
echo ""

echo "[5] Directory Permissions:"
if [ -d ".next" ]; then
    ls -la .next/ | head -n 5
fi

echo "==========================================="
echo "          END OF HOST DIAGNOSTIC"
echo "==========================================="

Decision summary: which solution to apply#

flowchart TD START["💥 Rust Panic:\ninner_of_uppers_lost_follower"] --> A{"✅ Node.js\n18.17 or higher?"} A -->|No| A1["📦 Install\nNode.js 20 LTS"] A -->|Yes| B["pnpm next clean\n(or rm -rf .next)"] B --> C{"pnpm dev\nResolved?"} C -->|Yes| C1["✅ Corrupted cache\nAdd dev:clean script"] C -->|No| D["Close IDE\nSuspend watchers"] D --> E{"Resolved?"} E -->|Yes| E1["✅ IDE file locking\nissue resolved"] E -->|No| F["Remove\n--turbo flag"] F --> G{"Resolved?"} G -->|Yes| G1["✅ Compiler version bug\nUse Webpack fallback"] G -->|No| H["pnpm update\nnext@canary"] H --> I{"Resolved?"} I -->|Yes| I1["✅ Bug already patched\nin canary"] I -->|No| J["🐛 Create GitHub issue\nwith RUST_BACKTRACE=1"] style START fill:#7f1d1d,color:#fca5a5 style C1 fill:#14532d,color:#86efac style E1 fill:#14532d,color:#86efac style G1 fill:#14532d,color:#86efac style I1 fill:#14532d,color:#86efac style J fill:#78350f,color:#fde68a

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments