Magic Tools
Pitfall NotesBy CooconAugust 14, 2026272 views4 min read

Fable 5 Has a 1M Context Window, So Why Does the Status Line Say 200k? Capture the Data Before You Swap the Tool

Symptom

Working in Claude Code with Claude Fable 5, whose docs clearly state a 1M-token context window (on by default, no beta header needed). But the custom status line at the bottom showed:

magictools | Fable 5 | Ctx 38% (76k/200k)

A 200k denominator. By that math, the bar would turn red at 160k tokens — while the real window still had 840k of headroom.

The first instinct was natural: maybe my statusline script is just bad, should I switch to a popular community one?

That instinct is the first pitfall this postmortem covers.

Think First: Would Switching Statuslines Even Help?

Claude Code's statusline mechanism works like this: on every refresh, it pipes a JSON payload via stdin to whatever command you configured, carrying model info, working directory, context usage, and so on. Every statusline — your own bash script or a community project — receives the same stdin.

There are only two places a window size can come from:

  1. The context_window.context_window_size field in stdin
  2. A built-in "model → window size" lookup table keyed by model name

If the field itself is wrong, any statusline that reads it is the same soup in a different bowl. And if you are counting on a built-in table, a freshly released model like Fable 5 almost certainly is not in third-party tables yet.

Conclusion: verify the data source before talking about switching tools. Otherwise you swap everything out and still see 200k.

Capture the Scene: One Line of tee for Hard Evidence

The statusline's stdin is ephemeral — you never see it normally. The most direct debugging move is a temporary line that dumps every input to disk:

input=$(cat)
printf "%s" "$input" > /tmp/statusline-debug.json   # temporary debug line, delete after use

Wait for one status line refresh (any new message in the session triggers it), and the file contains the genuine article:

jq '.model, .context_window' /tmp/statusline-debug.json
{
  "id": "claude-fable-5",
  "display_name": "Fable 5"
}
{
  "total_input_tokens": 76334,
  "context_window_size": 200000,
  "used_percentage": 38
}

Hard evidence: the model id is clearly claude-fable-5, yet Claude Code reports context_window_size as 200000. The script was not miscalculating — the upstream data source was wrong. When Claude Code does not know a new model's window size, it falls back to the 200k default (the known issue #76751, where 1M sessions get misreported as 200k).

At this point, the "switch statuslines" option is officially dead: whoever reads that field gets 200000.

Root Cause

Three layers stacked up:

  1. The official field misreports: Claude Code reports context_window_size as 200000 for 1M-window models (verified with claude-fable-5)
  2. The script's hardcoded fallback: when the field is missing, the script set ctx_size=200000, further cementing the value
  3. The existing correction was too strict: the script only corrected to 1M once "usage exceeded the reported size" — meaning you had to burn through 200k tokens before the denominator turned right. Until then, every percentage was computed against 200k and every red alert was a false alarm

The Fix: A Model Table Plus the Old Fallback

Since the upstream field cannot be trusted, maintain a table of known 1M-window models in the script and force-correct by model.id:

model_id=$(printf '%s' "$input" | jq -r '.model.id // empty')

# The official field misreports 200k for 1M-window models (#76751,
# verified: claude-fable-5 reports 200000).
# Force-correct known 1M-window models via this table; a [1m] suffix
# explicitly declares 1M and takes priority over the table.
case "$model_id" in
  *"[1m]"*)
    ctx_size=1000000
    ;;
  *fable-5*|*mythos-5*|*opus-5*|*sonnet-5*|*opus-4-8*|*opus-4-7*|*opus-4-6*|*sonnet-4-6*)
    if [ -z "$ctx_size" ] || [ "$ctx_size" -le 200000 ] 2>/dev/null; then
      ctx_size=1000000
    fi
    ;;
esac
[ -z "$ctx_size" ] && ctx_size=200000
# Fallback: for unknown models, correct to 1M when usage exceeds the reported window
if [ -n "$ctx_used" ] && [ "$ctx_used" -gt "$ctx_size" ] 2>/dev/null; then
  ctx_size=1000000
fi

Every model in the table (Fable/Mythos 5, Opus 5/4.8/4.7/4.6, Sonnet 5/4.6) has a documentation-confirmed 1M window. The old "correct when usage exceeds the reported value" logic stays as the fallback for models outside the table.

Verification needs no live session — the debug JSON captured earlier is a ready-made test case:

bash ~/.claude/statusline-command.sh < /tmp/statusline-debug.json
# magictools | Fable 5 | Ctx 7% (79k/1M)

Same input: 38% becomes 7%, and the denominator goes from 200k to 1M. Remember to delete the debug line and the dump file in /tmp afterwards.

An Easily Missed Tail: 1M ≠ 1M Usable

Claude Code reserves a buffer for auto-compact, so the practical budget of a 1M window is roughly 830k. With the status line computing percentages against 1M, keep one notch of margin in your head: when it turns red at 80%, start wrapping up — do not wait for 100%.

Lessons to Take Away

  1. Before swapping tools, confirm whether the data source is what is broken. When every downstream consumes the same upstream data, replacing the downstream is a no-op. Had I switched to a community statusline right away, it would still show 200k — plus one extra dependency.
  2. Persist ephemeral data before debugging it. For invisible scenes like stdin, pipes, and hook inputs, one line of tee/redirect to disk beats staring at outputs and guessing. The captured scene doubles as a regression-test input.
  3. Every hardcoded fallback needs an expiry plan. ctx_size=200000 was correct the day it was written and became a landmine after models iterated. Next to a fallback value, keep an identifier-keyed correction table, plus dynamic correction logic for anything outside the table.
  4. When a new model ships, treat your toolchain's knowledge of it as unverified. Model capabilities upgrade; the hardcoded assumptions about them in editors, CLIs, and monitoring scripts (window size, pricing, tokenizer) do not update themselves.

Related Articles

DeepSeek Harness Test: One Model, Three Harnesses — Claude Code 15/15, Codex CLI 15/15, Bare API 0/15 (and 5 Fake "Done"s)

DeepSeek Harness Test: One Model, Three Harnesses — Claude Code 15/15, Codex CLI 15/15, Bare API 0/15 (and 5 Fake "Done"s)

Same DeepSeek model (deepseek-v4-pro), three harnesses, five tasks (read / write / edit / run a command / multi-step), three rounds each, every side effect checked on disk. Claude Code on DeepSeek's Anthropic endpoint: 15/15, median 4.17s, ¥0.159 per task. Codex CLI 0.157.1 on the Responses endpoint: 15/15, median 15.52s, ¥0.022 per task — one seventh. Bare chat/completions: 0/15, and 5 of those rounds replied DONE or EDITED with nothing on disk. The differences are the harness: DeepSeek partitions its prompt cache by metadata.user_id, so every `claude -p` pays ~15K uncached tokens; Codex has no file tools and does everything through shell; on HTTP 500 Claude Code retries 10 times over ~175s while Codex quits in ~25s, and on 429 Codex doesn't retry; on 120KB of output Claude Code shows the first 2KB, Codex head + tail. And wire_api = "chat" is gone in Codex 0.157.1 — use responses.

claude-codeprompt-caching+6
hands-onSep 28, 202613 min
22
Claude Code "bash denied by auto mode": Why It Blocks, "could not evaluate" and "unavailable for this model" Tested

Claude Code "bash denied by auto mode": Why It Blocks, "could not evaluate" and "unavailable for this model" Tested

In auto mode, a blocked Bash call usually shows one of three messages: denied by auto mode, Auto mode could not evaluate this action, or auto mode unavailable for this model. I ran 40-odd real sessions on Claude Code 2.1.280. denied means the classifier judged the action out of scope, most often [Code from External]: it would run external code you never named. Retrying won't help. Name the source in your prompt, or declare it trusted in autoMode.environment in user-level settings. could not evaluate means the classifier returned no usable verdict. unavailable for this model means the model is older than claude-opus-4-6; under claude -p it is silently downgraded, with only a WARN line in the debug log. In 2.1.280 the verdict is computed server-side and returned with the main response. Behind a relay gateway that only admits Claude Code clients, the local fallback classifier request gets a 503, which is a reliable cause of "temporarily unavailable (server error)".

claude-codepermissions+4
pitfallsSep 27, 202611 min
30
Claude Code "Invalid API key · Fix external API key": Not logged in, Credit balance is too low, API Error 401/429/529 — Exact Messages and Retry Behavior, Tested

Claude Code "Invalid API key · Fix external API key": Not logged in, Credit balance is too low, API Error 401/429/529 — Exact Messages and Retry Behavior, Tested

28 cases, 61 claude -p runs on Claude Code 2.1.280 against a local Messages API stub. A 401 is retried 10 times, so Invalid API key · Fix external API key shows up after ~3 minutes; a key with non-ASCII chars or an embedded newline is rejected locally with 0 requests in 0.28 s. Every error goes to stdout, stderr is 0 bytes, exit 1, and JSON subtype still says success. CLAUDE_CODE_MAX_RETRIES=0 fails a 401 in 0.28 s. Set both KEY and TOKEN and both headers are sent.

claude-codetroubleshooting+4
pitfallsSep 27, 202611 min
44
Claude Code "Command timed out after 2m 0s": Two Timeout Paths, BASH_DEFAULT_TIMEOUT_MS and run_in_background Tested

Claude Code "Command timed out after 2m 0s": Two Timeout Paths, BASH_DEFAULT_TIMEOUT_MS and run_in_background Tested

Claude Code's Bash tool times out after 120 seconds by default. I ran 19 real sessions on 2.1.280 and found two timeout paths: only commands whose first word is sleep get killed with Exit code 143 / Command timed out after 2m 0s; everything else is moved to the background and killed 5 seconds after claude -p winds down. Either way claude exits 0, stderr is 0 bytes and the JSON top level says is_error=false. BASH_DEFAULT_TIMEOUT_MS=8000 killed at 8.17s; 0 or abc silently fall back to 120s; an explicit timeout above BASH_MAX_TIMEOUT_MS was silently clamped to 15s.

claude-codetroubleshooting+4
pitfallsSep 26, 20269 min
49

Published by Magic Tools