diffctx

Token counting and the raw diff bundle

Reference for two questions the CLI answers only in passing: whose tokens --budget counts, and what --with-raw-diff costs.

Which tokenizer diffctx uses

Every token number diffctx prints or enforces comes from tiktoken’s o200k_base encoder (the GPT-4o / GPT-4.1 tokenizer); the stderr summary names it. Counts are exact only for OpenAI models on that encoder — Claude, Gemini, Llama and friends tokenize differently, typically within single-to-low-double-digit percent either way.

--budget N therefore means exactly N o200k_base accounting tokens, and every artifact says so: the provenance block of the JSON/YAML output carries the effective configuration hash, and DIFFCTX_PROVENANCE=full adds the whole record — tokenizer id, every parameter, the resource caps — so a consumer converting to another model’s count knows what it is converting from. The full record costs ~500 tokens, which is why it is opt-in.

For a non-OpenAI consumer leave headroom — e.g. --budget 28000 when aiming at a 32k window — or set DIFFCTX_TOKEN_SAFETY_FACTOR (a multiplier ≥ 1.0, default 1.0): every count is scaled by it, so the budget is enforced against the inflated numbers and the factor is recorded in provenance. diffctx ships no model-specific default for it on purpose — the right margin is the consumer’s own measurement, and eval/tools/token_count_validate.py is the script that takes it against a model’s real token-count endpoint. If a hard guarantee matters, measure the produced file with your own model’s tokenizer.

There is no --tokenizer flag: o200k_base is pinned (locked by test_tiktoken_o200k_base_encoding_is_pinned) because every number in the paper’s evaluation is denominated in it.

What --budget does when you omit it

Omitting --budget does not mean “no limit” — it sizes one from the change:

auto = clamp(3.0 * sum(min(core_tokens, 1500) for each changed fragment), 8_000, 48_000)

Each changed fragment contributes at most 1 500 tokens, so one 2 000-line unparsed template touched by a single hunk cannot drag in the whole repo as “context”. The result is clamped to [8 000, 48 000].

On large real-world diffs it saturates at the 48 000 ceiling, and output stays within whatever budget it is given. It did not always: until #241 the budget bounded only the fragments, so the change summary — commit message plus the changed/deleted/renamed/lockfile/ignored path lists — rendered for free and was printed twice, once in full and once again as a “not represented” footer. On an 83-file range that put --budget 1000 at 3 150 tokens and --budget 0 at 2 464, with the selection dutifully under budget the whole time. The summary is now charged before selection starts and printed once, with omitted entries marked in place.

One consequence is deliberate: when the summary alone exceeds the budget, the summary is what you get and no fragment is selected. A changed path is never dropped to fit — a reader who cannot see what changed is worse off than one who is over budget — so on a very wide change a very small --budget is a request the artifact cannot honour, and it says so by containing nothing else.

Whether 48k is the right ceiling is open (#167). The measured trade on react-native, all 35 cases scored twice: dropping to --budget 8000 costs 0.145 recall and gains 0.118 precision for a fifth of the tokens, with the forbidden-file rate falling by nearly two thirds. That is a real trade rather than a free win — 20 of 35 cases lose some recall — so the constant has not been changed on that evidence alone.

--budget -1 disables the limit entirely. Do not reach for it on a wide range: selection and the post-passes dominate wall clock there, and with nothing to stop the greedy the run tends to hit the deadline instead of finishing.

--with-raw-diff

diffctx . --diff HEAD~1 --with-raw-diff writes git’s own unified diff into the output ahead of the selected fragments, so a reader gets the literal +/- edit alongside the surrounding code that explains it. One command replaces the hand-assembled git diff + diffctx bundle.

Rendering per format: a fenced ` ```diff ` block under a ## Raw diff heading in Markdown, a raw_diff block scalar in YAML, a raw_diff string field in JSON, and an indented raw diff: section in text — always ahead of the fragments.