pcdlint (Prompt-Cache Determinism Linter)
A static taint analysis linter that detects code patterns silently invalidating LLM Prompt Caching (OpenAI & Anthropic SDKs).
The $10,000 Bug Explained
LLM prompt caching works by storing the prefix of a prompt in a fast cache. When the prefix matches a cached version, the model skips re-processing the prefix — delivering a 10x read discount versus the full write cost.
The catch? The cache key is computed from the exact byte sequence of the prompt prefix. A single non-deterministic character at position 0 — like datetime.now() — changes every single byte, making the cache completely useless.
Full Write Cost: ██████████ ($0.030 / 1K tokens)
Cache Read Cost: █ ($0.003 / 1K tokens) ← 10x cheaper
One datetime.now() at character 0 silently destroys cache hits, wasting money on every request.
❌ Bad Code (0% Cache Hit)
from datetime import datetime
system = f"Time: {datetime.now()}\n{STATIC_RULES}"
# ^^^^^^^^^^^^ PREFIX TAINTED — every request has a unique prefix
client.chat.completions.create(
model="gpt-4",
system=system, # Cache MISS every time!
)
✅ Good Code (90% Cost Reduction)
from datetime import datetime
system = f"{STATIC_RULES}\nTime: {datetime.now()}"
# ^^^^^^^^^^^^^^ Static prefix first, dynamic value at the end — cache prefix preserved
client.chat.completions.create(
model="gpt-4",
system=system, # Cache HIT — prefix bytes match across requests!
)
Rule Reference
| Rule ID | Name | Severity | Description |
|---|---|---|---|
PCL001 |
prefix-taint-injection |
ERROR | Dynamic value placed before static prompt text invalidates cache prefix |
PCL002 |
unsorted-json-in-prefix |
WARNING | json.dumps() without sort_keys=True produces non-deterministic output |
PCL003 |
set-iteration-in-prompt |
ERROR | Python's PYTHONHASHSEED randomizes set iteration order across processes |
PCL004 |
dynamic-tools-mutation |
WARNING | Changing tool definition order invalidates the entire prompt cache hierarchy |
Installation
pip install pcdlint
Or install from source with dev dependencies:
pip install -e ".[dev]"
CLI Usage
# Check a single file
pcdlint check src/my_app.py
# Check multiple paths
pcdlint check src/ tests/
# Output as JSON
pcdlint check src/ --format json
# Fail on warnings (useful for CI)
pcdlint check src/ --fail-on-warn
GitHub Actions CI Integration
name: pcdlint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pcdlint
- run: pcdlint check src/ --fail-on-warn
How It Works
- AST Parsing: Uses Python's built-in
astmodule to parse source files — no external compilers needed. - Taint Tracking: Identifies non-deterministic sources (
datetime.now(),uuid.uuid4(),os.urandom(), etc.) and tracks their propagation through f-strings, concatenation, and.format(). - Prefix Analysis: Precisely determines if a tainted expression appears before a static prefix solid in the prompt string.
- Rule Matching: Checks tainted prompts against LLM API sink calls (
chat.completions.create,messages.create, etc.) and reports violations.
Metadata
Release files for pcdlint 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pcdlint-0.1.0.tar.gz | 20.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pcdlint-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.2 kB
Release files / pcdlint-0.1.0.tar.gz
| Download URL | pcdlint-0.1.0.tar.gz |
|---|---|
| Size | 20.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2b1e44c66a93092f7b03b025165fc5f16ea016f6bdbc7dfb8f78880bbaa26e69
|
|
BLAKE2b-256 checksum How to use checksums |
6c8d0ae2b7d3f0f56fff4475e8c380fc5a9c704da1bcbb04a416fd765acc4634
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / pcdlint-0.1.0-py3-none-any.whl
| Download URL | pcdlint-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7e8cb88f720063717136ffd2e421adc0cf697bebf8330f73e9e2bfada459c27
|
|
BLAKE2b-256 checksum How to use checksums |
53332cd78b09c679a240295df46edf26b7b2f151b745cc4e64833aa7841bd1b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log