Skip to content

fix(pragent): bake the tiktoken encodings so a read-only install works (0.12.1) - #233

Merged
huhamhire merged 2 commits into
devfrom
fix/tiktoken-readonly-install
Sep 10, 2026
Merged

huhamhire merged 2 commits into
devfrom
fix/tiktoken-readonly-install

Conversation

@huhamhire

Copy link
Copy Markdown
Owner

Fixes #232 and prepares the 0.12.1 patch release.

The released 0.12.0 Windows build fails every /describe and /review with PermissionError under C:\Program Files, which makes the installed app unusable.

Cause

Not a permissions setting — a bad file inside the package.

litellm points TIKTOKEN_CACHE_DIR at its own litellm_core_utils/tokenizers/ and ships encoding files there so tiktoken works offline. But the files it ships fail the expected_hash pinned by the tiktoken it depends on — all three of them (litellm 1.99.0 / tiktoken 0.12.0). tiktoken therefore treats the cache as invalid on every run: it deletes the file and re-downloads it into site-packages. Writable while building, read-only once installed.

Not model specific: the reported trace is a CLI provider (o200k_base), but a gpt model resolves through encoding_for_model() to the same stale files.

Why it shipped

Worth stating, because it explains why the existing gates were useless here:

  • A dev machine never shows it — vendor/ is writable, so tiktoken repairs itself on first run and stays fine forever.
  • CI never showed it either — the build machine can write too, so the runtime assembled and smoke-tested clean. A stale file loads wherever it can be re-downloaded, so "CI passed" said nothing about the installed copy.

Fix

scripts/tiktoken-cache.py:

  • prime (assemble time, before slimming) — repairs the bundled bytes so the cache is a hit and nothing is written at runtime, which also makes it work offline;
  • verify (smoke test, after slimming) — compares the bundled bytes against tiktoken''s pinned hashes.

Two deliberate choices:

  • verify compares hashes rather than loading an encoding. Loading is exactly the check that fails to discriminate here — it passes on any machine that can re-download. Comparing against the pin tests what actually decides fetch-or-reuse at runtime.
  • Expected hashes are read from the installed tiktoken, never copied into our source. A hash pasted here would drift on the next tiktoken upgrade and quietly reintroduce the bug this exists to prevent.

verify runs after slimming, so a slim rule that deletes the cache is caught too.

Verification

Four steps, closing the loop in both directions:

Check Result
Restore litellm''s stale copy, run verify fails, naming the mismatch — the new check does catch the 0.12.0 build
Run prime both encodings repaired, .tmp leftovers cleaned
Load encodings from a genuinely read-only dir (write denied via icacls) succeeds
Put the stale file back in that same read-only dir reproduces the reported PermissionError exactly

lint / typecheck / test / build pass.

Rule recorded

Same class as the pre-written .secrets.toml already handled in the assembler — a dependency writing under its own package at runtime. That was fixed as a one-off without becoming a rule, which is why this recurred. Now in AGENTS.md (engineering maintenance pitfalls) and docs/arch/02-agent/05-pragent-runtime.md: when adding or upgrading a Python dependency, check whether it writes under its own package at runtime; if so pre-populate it at assemble time and assert it, because the build machine can always write.

Release

This branch also carries the chore(release): 0.12.1 commit (version, both changelogs with compare links, lockfile). After merging to dev, the release flow is dev → master, tag v0.12.1, back-merge, bump.

🤖 Generated with Claude Code

huhamhire and others added 2 commits September 10, 2026 09:54
Every /describe and /review on an installed Windows build died with
PermissionError under C:\Program Files. The cause is a bad file in the package,
not a permissions setting.

litellm points TIKTOKEN_CACHE_DIR at its own tokenizers/ directory and ships
encoding files there to be offline-capable, but the files it ships fail the
expected_hash pinned by the tiktoken it depends on -- all three of them. tiktoken
therefore treats the cache as invalid on every run and re-downloads into
site-packages, which is writable while building and read-only once installed.
It is not model specific: every path that counts tokens goes through it.

scripts/tiktoken-cache.py prime repairs the bundled bytes at assemble time, so
the cache is a hit and nothing is written at runtime. verify asserts it, and
runs after slimming so a slim rule that deletes the cache is caught too.

The assertion compares hashes rather than just loading the encoding, because
loading proves nothing here: a stale file loads fine on a machine that can
re-download it. That is exactly how 0.12.0 shipped -- assembled clean, smoke
tested clean, then failed on the first run of every install. Expected hashes are
read from the installed tiktoken rather than copied here, so they cannot drift
from it on the next upgrade.

Documents the general rule in AGENTS.md and the runtime design doc: the install
directory is read-only, a dev machine never shows this because vendor/ is
writable, so a dependency that writes under its own package must be
pre-populated at assemble time and asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Patch release for the read-only install failure (#232): version, changelog
section in both locales with their compare links, lockfile synced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@huhamhire huhamhire added bug Something isn't working release 发布 PR / 版本发布 labels Sep 10, 2026
@huhamhire
huhamhire merged commit 14b4a1c into dev Sep 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working release 发布 PR / 版本发布

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant