Cursor-agent CLI: TUI fails with `NGHTTP2_ENHANCE_YOUR_CALM` once skill payload exceeds ~11 MB; no scoping option

Where does the bug appear (feature/product)?

Cursor CLI

Describe the Bug

cursor-agent CLI’s TUI silently fails — every prompt produces an endless “Reconnecting…” spinner — when the combined size of all discovered SKILL.md files exceeds ~11 MB. Threshold is byte-based, not count-based: 220 small skills (50 KB each) and 32 large skills (1 MB each) both trigger it.

Underlying server rejection (visible only via agent --debug): NGHTTP2_ENHANCE_YOUR_CALM (HTTP/2 flow-control). The CLI retries 10 times then surfaces Connection failed repeatedly. With network.useHttp1ForAgent: true the same root cause surfaces more cleanly as Bad Request: Append data exceeds maximum size of 52428800 bytes against api2.cursor.sh.

The skill scanner uses recursive globs (**/.agents/skills/**/SKILL.md, **/.cursor/skills/**/SKILL.md, **/.claude/skills/**/SKILL.md, **/.codex/skills/**/SKILL.md, **/AGENTS.md, **/CLAUDE.md) and bundles every match into the per-turn request payload. There is no .cursorignore honoring on the skill loader, no env var, no config to scope skill discovery.

Multi-agent skill packs make this trivial to hit. gstack (GitHub - garrytan/gstack: Use Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA · GitHub) ships every skill 9× — one canonical + per-runtime wrappers for .hermes, .openclaw, .opencode, .factory, .gbrain, .kiro, .slate, .cursor, .agents. A single gstack install with ~43 skills becomes ~390 SKILL.md (~28 MB) because the recursive glob matches every wrapper. On my machine: 1408 SKILL.md loaded, ~77 MB skill payload, TUI completely unusable.

Steps to Reproduce

  1. Plant 250 synthetic SKILL.md files of ~50 KB each:
mkdir -p ~/.agents/skills/_repro
for i in $(seq 1 250); do
  mkdir -p ~/.agents/skills/_repro/skill_$i
  python3 -c "print('---\nname: s$i\ndescription: x\n---\n' + 'x' * 49000)" \
    > ~/.agents/skills/_repro/skill_$i/SKILL.md
done
  1. Launch TUI with debug logs:
agent --debug --model composer-2.5
  1. Type any prompt (e.g. “what is 2 plus 2”) and press Enter
  2. Spinner shows “Composing” then UI freezes on “Reconnecting…”
  3. Inspect $TMPDIR/cursor-agent-debug-*/session.log:
AgentSkillsCursorRulesService load completed { ruleCount: 1408, skillCount: 1408 }
[nal_agent_retries] Initial request { attempt: 0 }
ConnectError: Stream closed with error code NGHTTP2_ENHANCE_YOUR_CALM
[nal_agent_retries] Error not retryable
turn.outcome { outcome: "error", error_code: "ERR_HTTP2_STREAM_ERROR",
               error_text: "Connection failed repeatedly" }
  1. Cleanup: rm -rf ~/.agents/skills/_repro

Threshold sweep (byte-based, not count-based)

Held baseline constant (14 plugin/cloud-rule skills, ~0.7 MB), varied N synthetic SKILL.md files of 50 KB each. Binary-searched the boundary:

Synthetic added Total skills Total skill bytes Result
205 217 ~10.85 MB pass
206 218 ~10.90 MB pass
207 219 ~10.95 MB pass
208 220 ~11.00 MB fail — NGHTTP2_ENHANCE_YOUR_CALM
275 287 ~14.4 MB fail
500 512 ~25.6 MB fail
32 × 1 MB 32 ~20 MB fail
62 × 1 MB 62 ~50 MB fail

Failure is byte-based: 32 × 1 MB (20 MB total) fails the same as 220 × 50 KB (11 MB total). Threshold sits at ~11 MB on the default HTTP/2 endpoint (agentn.global.api5.cursor.sh). A separate higher 50 MB hard limit exists on api2.cursor.sh (HTTP/1 path).

Expected Behavior

Either of these would fix it cleanly:

  1. cursor-agent honors .cursorignore (or a new .skillignore) in the SKILL discovery glob, so users can scope the scan. Currently ignoreService only gates file-read / grep operations — extend it (or add a parallel filter) into AgentSkillsCursorRulesService.loadSkillsFromDirectory and the recursive glob.

  2. cursor-agent supports a config option in ~/.cursor/cli-config.json to scope skill roots:

    {
    “agentSkillRoots”: [“~/.cursor/skills-cursor/”, “~/.agents/skills/gstack/”],
    “agentSkillIncludeWrappers”: false
    }

    Default behavior unchanged; opt-in for users who hit the limit.

  3. At minimum (UX fix): when NGHTTP2_ENHANCE_YOUR_CALM or the 50 MB BAD_REQUEST fires, the TUI surfaces “Your skill bundle is too large (NN MB > 11 MB). Run agent --skills-list to prune.” instead of looping silently on “Reconnecting”. Right now the only signal is the spinner forever, sending users down the wrong debugging path (network, auth, MCPs).

Operating System

MacOS

Version Information

CLI:
CLI Version 2026.05.24-dda726e
Model Composer 2.5
Subscription Tier Pro
OS darwin (arm64, macOS 15.4)

For AI issues: which model did you use?

used all models.

mainly → Composer 2.5

For AI issues: add Request ID with privacy disabled

Request ID: c8a20849-a240-49d4-8399-028f92a871d9

Note: this run was captured with privacyMode: 2 (ghost mode) enabled, so the server-side trace may be unavailable. Happy to re-run with privacy disabled if needed — just let me know.

Additional Information

Workarounds tried (none fix the underlying limit):

  1. .cursorignore at the skill root — ignored by the SKILL scanner. Only ignoreService for file reads honors it. Skill count unchanged.

  2. network.useHttp1ForAgent: true — switches endpoint to api2.cursor.sh and surfaces the cleaner 50 MB BAD_REQUEST error, but does not raise the per-stream allowance.

  3. Removing one runtime’s gstack wrappers (1408 → 974 skills) — still fails (~50 MB payload, way over 11 MB threshold).

  4. agent --print (headless) — happens to succeed more often because each invocation opens a fresh short-lived stream that survives flow-control more often. Not a fix; same payload is sent.

Effective workaround: physically delete SKILL.md files from disk until total bytes drop below ~11 MB. On my machine I pruned 1408 → 218 SKILL.md (77 MB → 9.28 MB) by removing per-runtime gstack wrappers — after that, TUI works (turn.outcome: outcome=success). But this is destructive and breaks skills the user wants available to other agent runtimes.

Related: This pattern also hits Claude Code (less severely — Claude Code drops skills gracefully with a “N skill descriptions dropped” warning instead of freezing). Filed a related issue on the gstack side asking them to stop shipping 9× duplicates: Skill files generated for other AI hosts pollute Claude Code's skill scanner · Issue #1694 · garrytan/gstack · GitHub

Even after gstack restructures, the Cursor-side ask stands: any recursive skill loader with no scoping mechanism is vulnerable to this from any sufficiently large skill pack.

Does this stop you from using Cursor

Yes - Cursor is unusable

Hey, thanks for the report. It’s one of the cleanest I’ve seen. The binary search on the threshold, the split between HTTP/2 vs HTTP/1 paths, and the note that .cursorignore isn’t honored by the skill scanner all match what we see in the code.

The root cause is known. AgentSkillsCursorRulesService recursively scans skill directories with no size cap, no scoping, and no ignore file filtering. Dedupe only works for hardcoded prefixes .cursor, .claude, .codex, .agents, so gstack wrappers like .hermes, .openclaw get treated as separate skills and can easily inflate the payload past the roughly 11 MB HTTP/2 limit.

We’re already tracking this issue on our side. I’ll add your details to the ticket: the exact threshold, the repro script, and the note about ignoreService. They’re helpful, especially the binary searched boundary. I can’t share an ETA for a fix yet.

No need to re-run with privacy off. The code review covers everything.

Current workaround, which you already found but for the record: prune SKILL.md files to keep the total under 11 MB. Pre-runtime gstack wrappers are the first things to remove, since the canonical skill will still be picked up from the main directory. Filing an issue with gstack about the 9x duplication is the right move, it fixes the amplification at the source.

Privacy mode privacyMode: 2 doesn’t affect this. The problem is in the client-side scan before the request is sent.

If I get an update on the fix, We’ll post it here.