Magic Tools
Hands-OnBy CooconSeptember 20, 2026350 views7 min read

DeepSeek Says 1M, Claude Code Says 200K: I Measured Both and Neither Number Is the Real Limit

DeepSeek's pricing page lists a 1M context length for both deepseek-flash and deepseek-v4-pro. Point Claude Code at DeepSeek's Anthropic-compatible endpoint (three env vars — I covered the setup and what it costs you separately) and Claude Code reports this for the same model:

"contextWindow": 200000

Two numbers, 5x apart, describing the same model. Which one governs?

I measured it. The answer is neither, and the way it fails is more interesting than either number.

What I measured

Three separate questions that get conflated:

  1. Does the endpoint accept it? — a hard API limit.
  2. Does the model actually use it? — retrieval, not just acceptance.
  3. Does Claude Code let you get there? — the client-side gate nobody documents.

All runs: Claude Code 2.1.270, deepseek-flash, macOS, isolated CLAUDE_CONFIG_DIR. Needle-in-a-haystack uses a unique string planted at position zero (hardest position to retrieve when the haystack is huge) with the question at the very end. Every prompt carries a unique nonce so prompt caching can't contaminate the token counts — the first version of this experiment did not, and produced two rows with identical token counts for payloads differing 2x in size.

Layer 1: DeepSeek's real ceiling is 2^20, not "1M"

Ladder on the raw endpoint, needle at position zero:

Target Real input tokens HTTP Wall Needle found
200K 399,954 200 8.6s ✅
400K 799,823 200 15.8s ✅
950K 949,775 200 19.8s ✅
1040K 1,039,744 200 19.4s ✅
1048K — 400 3.7s —

At 1,039,744 tokens the model still pulled a string planted at the very beginning, in under 20 seconds. The long context is real, not a spec-sheet number.

The rejection tells you the exact limit:

This model's maximum context length is 1048576 tokens.

1,048,576 = 2^20. So "1M" is binary, not decimal — you get 48,576 tokens more than a literal million.

Context ladder on the raw endpoint: 1,039,744 tokens accepted with the needle retrieved; 1,048,576 is the hard ceiling

The limit includes your output budget

Worth knowing before you size a request. Same input, only max_tokens changed:

Input tokens max_tokens Result
1,045,710 1,000 ✅ 200
1,045,707 8,000 ❌ 400

The error spells out the arithmetic itself:

you requested 1053707 tokens (1045707 in the messages, 8000 in the completion)

So the budget is input + completion ≤ 1,048,576. Ask for a generous max_tokens on a near-full context and you get rejected on input that would otherwise have been fine.

Same input, only max_tokens changed: the 1,048,576 budget covers input plus completion

Layer 2: Claude Code refuses client-side, and it never calls the API

Pipe an oversized prompt into claude -p and you get:

result: "Prompt is too long"
is_error: true
duration_ms: 34
input_tokens: 0

34 milliseconds and zero tokens. Claude Code rejected it locally. DeepSeek never saw the request, and you were never charged.

So far so reasonable — it declared a 200,000 window, it is enforcing a window. Except the number doesn't match either.

Binary search on prompt size, repetitive English filler:

Target user tokens Result Real total input
30K ✅ pass 64,040
80K ✅ pass 114,024
85K ✅ pass 119,022
90K ❌ reject 0 (24ms)
95K / 105K / 112K / 120K / 140K / 150K / 180K / 195K / 205K ❌ reject 0

The gate fires at about 119K total tokens — roughly 60% of the 200,000 it advertises. (~34K of that total is Claude Code's own system prompt and tool definitions, which you pay on every turn.)

Layer 3: the gate isn't measuring tokens at all

Here is the part that matters. Claude Code cannot know DeepSeek's tokenizer, so it has to estimate. If the estimate is the usual characters ÷ 4 heuristic, then the gate should be fixed in characters and drift arbitrarily in real tokens depending on what your text is made of.

Test: same character count, wildly different tokenization density. Repetitive English runs about 5.4 chars/token here; random hex runs about 1.5.

Content Characters Result Real tokens
Repetitive English 459,850 ✅ pass 119,022
High-entropy hex 460,000 ✅ pass 298,819
Repetitive English 486,900 ❌ reject —

Same ~460,000 characters. One carried 119K tokens, the other 298,819 — and both passed. The gate did not notice a 2.5x difference in actual token count.

Narrowing the character threshold:

Characters Result Real tokens
470,000 ✅ pass 304,808
478,000 ✅ pass 309,567
482,000 ❌ reject —

The gate is at ~480,000 characters. Divide by 4 and you get 120,000 — the chars÷4 heuristic, hard-coded against a budget that is itself 60% of the declared window.

Same character count, 2.5x the real tokens, both pass: the gate counts characters, not tokens

Two consequences that actually bite:

  • The declared 200K window is not a ceiling. 478,000 characters of hex went through carrying 309,567 real tokens, 55% past the advertised window, and the needle came back correctly. Nothing broke — DeepSeek's real limit is 1M, so there was plenty of room.
  • The declared window is not a floor either. Ordinary English gets cut off at ~119K real tokens, well under 200K.

If your work is CJK text, minified code, base64 blobs, or logs — anything that does not tokenize at 4 characters per token — the number on your screen is not describing your situation in either direction.

Layer 4: in practice you never get near any of this

I built a 1.55MB file, 300K tokens, needle on the last line, and asked Claude Code to cat it. It answered correctly. But the usage said:

input_tokens: 34,973

The file never entered the context. Tracing the actual tool calls shows why:

[call]   Bash: cat tail-big.txt
[result] 1,915 chars — "<persisted-output>
         Output too large (1.5MB). Full output saved to: /Users/…"
[call]   Bash: tail -c 2000 tail-big.txt
[result] 1,999 chars — "…The maintenance access code … is PELICAN-7731."

Claude Code persists oversized tool output to a file and hands the model a ~2KB preview. The model then worked around it on its own with a targeted tail -c 2000. Good agentic behaviour — and it means the needle test told me nothing about long context until I noticed.

The threshold is exact:

Bash output Enters context
29,990 chars 29,989 chars — inline
30,010 chars 2,315 chars — persisted

30,000 characters. Any single Bash output above that is written to disk and summarized. Note this is a Bash threshold — in one run the model chose Read instead, which returned 53,656 characters inline for the same file. (That run briefly made my ladder look inconsistent; the cause was the model picking a different tool, not a different limit. I had to pin the tool choice to get a clean measurement.)

Bash output over 30,000 characters is persisted to a file; only a ~2KB preview enters context

So which number wins?

Neither. Ranked by what actually stops you first:

Limit Value Enforced by
Bash output inlining 30,000 chars Claude Code, per tool call
Prompt gate ~480,000 chars (chars÷4 ≈ 120K est. tokens) Claude Code, client-side, 25ms, no API call
Declared window 200,000 tokens nothing — neither floor nor ceiling in practice
Model hard limit 1,048,576 tokens (input + max_tokens) DeepSeek, HTTP 400

The advertised 1M is real and genuinely usable — against the raw API. Through Claude Code you will hit a character-counting gate at roughly a tenth of it, and hit output persistence long before that.

Practical takeaways

  1. Want the 1M? Call the endpoint directly. Claude Code will not take you there.
  2. Don't plan around contextWindow: 200000. It is not enforced as either a minimum or a maximum. The real client-side gate is ~480,000 characters.
  3. Non-English content makes the estimate worse, not better. Chinese, minified JS, base64 — all tokenize far from 4 chars/token, so the gate fires at a real token count that can be double what you expect, in either direction.
  4. Large files are not a context problem, they are a tool-output problem. Anything over 30,000 characters from Bash gets persisted. Use grep/tail/sed to extract what you need instead of cat-ing and hoping.
  5. Size max_tokens against the same budget as your input when you are anywhere near the ceiling.

Pitfalls, condensed

  1. Reusing filler across a ladder silently enables prompt caching — my first run reported identical token counts for payloads differing 2x. Add a unique nonce per request.
  2. A needle at position zero survives truncation — it proves nothing. Put it at the end, then verify the content actually entered context by reading input_tokens.
  3. The model may pick a different tool between runs (Read vs Bash cat), which changes the numbers completely. Pin the tool when you are measuring the tool.
  4. claude -p "$(cat big.txt)" dies with Argument list too long — that is your shell's ARG_MAX, not a Claude Code limit. Pipe via stdin.
  5. "1M" is 2^20 = 1,048,576, and it includes your completion budget.

Tested 2026-09-20, Claude Code 2.1.270 on macOS/Apple Silicon against deepseek-flash (DeepSeek-V4.1-Flash) via https://api.deepseek.com/anthropic. Total API spend for every experiment in this article: ¥5.36 ≈ $0.75, measured by account balance delta. Raw logs retained.

Get field notes like this every Saturday

Subscribe to Dev Breakfast: daily AI coding picks at 8:00, plus a Saturday roundup of this week's hands-on tests with Claude Code / Codex / local models. Written in Chinese.

Related Articles

Blocking rm -rf in Claude Code: A Deny Rule Stopped 14 Deletes in Two Weeks, 7 Other Spellings Still Get Through, and a Hook That Closes the Gap

To keep Claude Code from deleting things by accident, I added a deny rule, Bash(rm -rf *), to my global settings. Across my sessions from October 7 to 21 it blocked 14 deletes. After each denial the model picked a new directory name 8 times and dropped the delete 6 times. It never tried another spelling. Then I replayed 22 delete commands verbatim against Claude Code 2.1.285 through a stub. rm -fr, rm -r -f, rm -Rf and rm -r deleted the directory in manual, auto and bypass mode alike. /bin/rm, find -delete and bash -c deleted it in manual and bypass mode. In auto mode, rm -fr inside the working directory skips the classifier because it 'would be allowed in acceptEdits mode'. A second, built-in dangerous-rm check fires on targets like $D/*, even in bypass mode, but loses track of a variable assignment when a heredoc comes earlier in the same command. The post ends with a PreToolUse hook that blocks every recursive spelling in all three modes and tells the model what to do instead.

claude-codehooks+5
pitfallsOct 21, 20268 min
482

Claude Code Auto Mode with DeepSeek, GLM or Kimi: What Each "temporarily unavailable" Reason Means and When Retrying Is Pointless (Fault-Injection Test)

On a third-party model, Claude Code auto mode sends one extra classifier request per side-effecting action, from your session model to the same backend: a 132,470-character system prompt, about 29.7K input tokens per check. When it fails, you see "xxx is temporarily unavailable, so auto mode cannot determine the safety of Bash". We injected faults and recorded each message: 500 (server error), 429 (rate-limited), 529 (overloaded) and connection resets (connection failed) are retried 5 times; no reply in 60 s gives (timed out). **No parentheses means a 4xx rejection: tried once, and retrying never helps.** A reply with no verdict gives "could not evaluate"; three in a row bring back manual approval. CLAUDE_CODE_AUTO_MODE_MODEL has no effect. Claude Code 2.1.285, real DeepSeek v4-pro and MiMo v2.5 backends.

claude-codedeepseek+7
pitfallsOct 11, 202612 min
540

Claude Code "Prompt is too long" vs "maximum context length": Why Auto-Compact Works for One and Not the Other (Tested)

Claude Code decides a request was too long by matching the error text. If the backend says prompt is too long or input is too long for requested model, it auto-compacts the conversation and retries — invisible to you. DeepSeek and OpenAI-style gateways say This model's maximum context length is …, which it doesn't recognize: you get API Error: 400 on every turn. Manual /compact works; the better fix is to declare the real window with CLAUDE_CODE_MAX_CONTEXT_TOKENS (non-claude- model IDs) or CLAUDE_CODE_AUTO_COMPACT_WINDOW (claude- IDs) so it compacts before hitting the limit. Tested on Claude Code 2.1.285 against a local stub.

claude-codedeepseek+6
pitfallsOct 5, 20267 min
568

Claude Code Stuck on a Spinner With No Response: It Waits 6 Minutes per Attempt, and 10 Retries Can Hang It for Over an Hour (Tested)

When Claude Code spins without output or sits at Retrying in 0s, the backend is usually not sending anything. Tested on 2.1.285 (API key + ANTHROPIC_BASE_URL): with no response headers it waits 6 minutes (360 s) per attempt before timing out — setting API_TIMEOUT_MS to 600000 or 900000 doesn't change that; only lower values work. With the default 10 retries it can hang for over an hour. A gateway that buffers the reply until generation finishes will never deliver a reply that takes over 6 minutes. Press Esc to interrupt. All tested against a local stub.

claude-codetroubleshooting+5
pitfallsOct 5, 20269 min
478