[BUG] Orphaned background agent when context limit triggers early due to percentage mismatch

Resolved 💬 3 comments Opened Jan 18, 2026 by smconner Closed Jan 21, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

---

🐛 What's Wrong?

Background agents become orphaned and consume 100%+ CPU indefinitely when "Context limit reached" triggers prematurely due to the context percentage mismatch bug (#18241).

The cleanup mechanism that should terminate background agents appears to check a different context metric than the one triggering "Context limit reached":

┌─────────────────────────────────────────────────────────────────┐
│  System A (Input Gate)     →  "Context limit reached" triggered │
│  System B (Cleanup Gate)   →  Sees 15% free, no cleanup needed  │
│  Background Agent          →  Orphaned, runs forever            │
└─────────────────────────────────────────────────────────────────┘

---

📊 Evidence of Mismatch

/context output immediately before "Context limit reached":

Context Usage
claude-opus-4-5-20251101 · 168k/200k tokens (84%)

⛁ System prompt: 3.1k tokens (1.5%)
⛁ System tools: 16.3k tokens (8.1%)
⛁ Memory files: 13.8k tokens (6.9%)
⛁ Skills: 44 tokens (0.0%)
⛁ Messages: 134.5k tokens (67.2%)
⛁ Compact buffer: 3.0k tokens (1.5%)
⛶ Free space: 29k (14.7%)               ← Says 14.7% free

Context low (1% remaining)               ← Says 1% remaining

Then immediately on next tool call:

● Bash(ip netns list | grep detect)
  ⎿  No orphaned detect namespaces or processes
  ⎿  Context limit reached · /compact or /clear to continue

The Inconsistency

| Source | Value |
|--------|-------|
| Token math | 168k/200k = 84% used |
| Free space line | 14.7% free |
| Warning text | 1% remaining |
| Actual behavior | Limit reached |

These cannot all be true simultaneously.

---

💀 Observed Orphan Behavior

After "Context limit reached", the background process continued for 2+ hours:

| Metric | Value |
|--------|-------|
| PID | 4180639 |
| PPID | 1 (orphaned, adopted by init) |
| CPU | 109-111% sustained |
| CPU Time | 156 minutes accumulated |
| Threads | 18 |
| Memory | ~600MB RSS |
| Context Switches | 177,373 voluntary / 41,477 involuntary |
| State | Sleeping in ep_poll (waiting for input that never comes) |

Process only terminated when manually sent SIGUSR2 during investigation.

---

📋 Session Details

Session ID:     abdca6d7-a612-4545-87aa-00045d864820
Session start:  2026-01-18T06:01:47Z
Process start:  2026-01-18T06:02:56Z  (+1 min)
Last activity:  2026-01-18T08:23:59Z
Process killed: 2026-01-18T08:30:XXZ  (still at 111% CPU after 2+ hours)
Subagents:      5 spawned during session

---

✅ What Should Happen?

  1. Consistent metrics — All context percentage displays should agree (#18241)
  2. Atomic cleanup — When ANY "Context limit reached" fires, ALL background agents terminate
  3. No threshold dependency — Cleanup should trigger on the limit event itself, not a separate percentage check

---

🔗 Related Issues

| Issue | Title | Connection |
|-------|-------|------------|
| #18241 | Context percentage mismatch between /context, statusline API, and internal limit trigger | Root cause — mismatch prevents cleanup |
| #17391 | Orphaned processes after terminal close cause runaway memory consumption | Same symptom — different trigger |
| #18405 | Orphaned processes from subagents crashing computer | Same symptom — different trigger |

This bug is the intersection:

#18241's percentage mismatch prevents #17391's cleanup logic from triggering

---

🖥️ Environment

| Field | Value |
|-------|-------|
| Claude Model | Opus (claude-opus-4-5-20251101) |
| Claude Code Version | 2.1.x (current) |
| Platform | Anthropic API |
| OS | Ubuntu 24.04 (Linux 6.8.0-90-generic) |
| Terminal | VSCode integrated terminal |

---

🔍 Additional Notes

  • Orphaned process was in ep_poll state — waiting for epoll events that never arrive
  • Responded to SIGUSR2 by terminating (no custom handler) — confirms it wasn't stuck, just abandoned
  • No stack trace available — process exited on signal before gdb could attach
  • The 156 min CPU time vs 142 min wall time suggests sustained multi-core usage

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗