Troubleshooting¶
Common problems and how to resolve them.
Authentication / Missing API Key¶
- Symptom: Errors like "GOOGLE_API_KEY not found", "HUGGING_FACE_TOKEN environment variable not set", or provider import failures.
- Fix:
- Create a
.envfile in the directory where you run the command (or a parent directory). -
The CLI automatically loads it. Example:
-GOOGLE_API_KEY="AIza..." OPENAI_API_KEY="sk-..." ANTHROPIC_API_KEY="sk-ant-..." HUGGING_FACE_TOKEN="hf_..." OPENROUTER_API_KEY="sk-or-..." OLLAMA_HOST="http://localhost:11434" ANONYMIZER_MAPPING_KEY="a long secret" ANONYMIZER_FAKE_SECRET="another long secret"ANONYMIZER_MAPPING_KEYlocks mapping files as*.mapping.json.enc.ANONYMIZER_FAKE_SECRETseeds--operator TYPE=fake. Neither is required. - For the SDK, load the environment variables yourself (e.g. withpython-dotenvoros.environ).
HTTP service is missing¶
- Symptom:
pdf-anonymizer-apiis not installed. - Fix:
pip install pdf-anonymizer-api. Thenpdf-anonymizer-api --host 127.0.0.1 --port 8000. There is no authentication. See HTTP service and Docker.
Ollama Not Running or Model Not Found¶
- Symptom: Connection errors or "model not found" when using
ollama/...models. - Fix:
- Start the Ollama server:
ollama serve - Pull the model first:
ollama pull phi4-miniorollama pull gemma:7b - Check
OLLAMA_HOSTif Ollama is not on the default port.
Rate Limits, Timeouts, or Retries¶
- Symptom: Intermittent failures, "rate limit", 429, or empty results on long documents.
- Fix:
- Use a more generous profile:
-p best-quality(more retries, smaller chunks). - Or override manually with a larger model / more retries via SDK
max_retries. - The library uses exponential backoff + jitter for transient errors (rate limits, server errors, connection issues, JSON parse errors). Auth errors are not retried.
Nothing Was Anonymized / Very Few Entities Found¶
- Symptom: Output looks almost identical to the input.
- Possible causes & fixes:
- The document contains very little PII (or the LLM prompt style was too conservative).
- A sentence may still point to one person without writing their name (an identity clue). The default
best-speedprofile does not hunt for those. Try-p best-quality(uses thedetailedprompt and a stronger model by default). - Use
--anonymized-entitiesonly if you intentionally want to restrict the types. - Check
app.logβ it shows how many entities were found by Regex vs LLM per chunk. - A number that looks like a card, IBAN, VIN, or national ID but fails the extra check-digit is still hidden, as
IBAN_LIKE_1/CREDIT_CARD_LIKE_1and so on. That is expected for typos and example numbers such as1234-5678-9012-3456. - After a run, check
data/stats/<stem>.residual_pii.json(orpdf-anonymizer verify β¦). Leftover emails or numbers listed there were not hidden. - Check
data/stats/<stem>.risk.json(orpdf-anonymizer report β¦). A high score means leftover identity clumps (job + company + place), not that names were missed. - A keep-list phrase stays visible on purpose. A deny-list phrase becomes
CUSTOM_neven if regex and the model missed it. - Very short documents or unusual formatting can reduce recall.
Encrypted mapping will not open¶
- Symptom:
deanonymizefails on*.mapping.json.enc, or you only have a.encfile and no passphrase. - Fix:
- Pass the same
--mapping-passphraseyou used withrun, or setANONYMIZER_MAPPING_KEY. - If you passed
--source-sha256, it must be the SHA-256 of the original source file that was locked, not the anonymized file. - There is no recovery path. The masked document plus a locked map without the passphrase cannot put names back.
- Default
runstill writes plaintext*.mapping.jsonif you set no passphrase. --ephemeral-mappingnever wrote a map. That run cannot be reversed from disk.
A date or ZIP cannot be restored uniquely¶
- Symptom: After
--operator DATE=generalize(or HIPAA year-only dates), two different originals both became2019. - Fix: That is expected. Generalize, mask, and hash are not always one-to-one. Use
replace(the defaultPERSON_1) when you need a unique round-trip.
--entity-profile hipaa-safe-harbor is not a certificate¶
- That flag is a coverage aid (broader identifier classes, year-only dates, ZIP3, age 90+).
- It does not mean the file is legally de-identified. It does not hide pixels in a photo. A person still has to read the result.
LLM Returns Invalid JSON / Parsing Failures¶
- Symptom: Errors about JSON decode or Pydantic validation in the logs.
- Fix:
- The library already retries on parsing errors (the LLM sometimes returns markdown fences or extra text).
- If it keeps happening, switch to a stronger model (
-p best-quality) or a model known to follow JSON instructions well. - The prompts are designed to return only a JSON object.
Cache Problems or Stale Results¶
- Symptom: Changes to prompts or documents are ignored, or you want a completely fresh run.
- Fix:
- Delete or rename
data/cache/llm_responses.json. -
Or disable caching programmatically:
Scanned PDF is refused¶
- Symptom:
This PDF has pages but no extractable text (likely a scan). - Fix: Install Tesseract on PATH and re-run with
--ocr, or supply a PDF that already has a text layer. The tool will not write an empty βsuccessβ file.
Tesseract missing¶
- Symptom:
OCR requires the Tesseract binary on PATH. - Fix: Install the Tesseract system package (
apt-get install tesseract-ocr/brew install tesseract). There is no pip extra for this.
Native PDF still shows a name in an image¶
- Symptom: A photo or letterhead still shows a name after
--output-pdf. - Fix: The digital path excises text glyphs. It does not OCR images. Delete image-only letterhead, or accept the residual.
--output-pdf rejected on a text file¶
- Symptom:
--output-pdf only applies to PDF inputs. - Fix: That flag rewrites a PDF package. Use it on a
.pdf.
Large Files / Memory or Context Issues¶
- Symptom: Out of memory, context length errors, or very slow runs.
- Fix:
- Use
-p best-cost(larger chunks). - Manually increase
--characters-to-anonymize(e.g. 120000 or higher) when using a model with a large context window. - The tool uses Markdown-aware splitting for PDFs and
.mdfiles to preserve structure. - CSV and Excel are in-memory. They are refused above 50 MiB or 500,000 non-empty cells. That is not the 1 GB text-chunking path.
- Word
.docxis in-memory. It is refused above 50 MiB or 100,000 non-empty paragraphs. That is not the 1 GB text-chunking path.
Excel extra missing¶
- Symptom:
Excel support requires the extra: pip install "pdf-anonymizer-core[excel]" - Fix:
pip install "pdf-anonymizer-core[excel]"orpip install "pdf-anonymizer-cli[excel]". CSV does not need this extra.
Word extra missing¶
- Symptom:
Word support requires the extra: pip install "pdf-anonymizer-core[docx]" - Fix:
pip install "pdf-anonymizer-core[docx]"orpip install "pdf-anonymizer-cli[docx]".
Rejected Word formats¶
- Symptom:
.doc,.docm,.dot,.dotm, or.dotxis rejected with a convert-to-docx message. - Fix: Re-save as
.docx. Macro-enabled documents are not supported (macros can re-derive PII).
Rejected spreadsheet formats¶
- Symptom:
.xls,.xlsm,.ods, or.xlsbis rejected with a convert-to-xlsx / export-CSV message. - Fix: Re-save as
.xlsxor export CSV. Macro-enabled workbooks are not supported (macros can re-derive PII).
Formulas are dropped¶
- Symptom: An Excel formula is gone; a CSV cell that started with
=now starts with'. - Fix: That is intended. Excel writes cached values only so
=A1cannot restore a replaced name. CSV prefixes'on cells whose raw value starts with=.+1-555-0100is a phone, not a formula, and is left untouched.
Charts, comments, and other leftovers¶
- Symptom: A name still appears in a chart, comment, header/footer, data-validation list, defined name, or hyperlink.
- Fix: On spreadsheets, those surfaces are not walked. Delete charts and clear headers/comments before sharing, or accept the residual. On Word, headers, footers, comments, field codes, and hyperlink targets are walked. Images, alt text, core properties (author), charts, and embedded objects are not.
Word formatting after the first run is gone¶
- Symptom: A sentence that was partly bold or a second color is now one style.
- Fix: Word splits one phrase across many runs. Replacement writes the new text into the first run and clears the rest, so later-run formatting is lost. Paragraph style is kept.
Undashed numeric IDs missed on --no-llm¶
- Symptom: An Excel integer such as
123456789is still the same integer after--no-llm. - Fix: Regex does not run on number or date cells (it would shred employee IDs and quantities). Store the dashed form as text, use a deny-list, or keep the language model on. There is no β9-digit integer β SSNβ rule.
Stored value, not display format¶
- Symptom: A numeric cell formatted as
000-00-0000was not treated as a dashed SSN. - Fix: Detection uses the stored value (
123456789), not Excelβs display format.
Output Files Not Where Expected¶
- All artifacts are written relative to the current working directory:
data/anonymized/data/mappings/data/deanonymized/data/stats/- These directories are created automatically.
Still Stuck?¶
- Look at
app.log(always written alongside console output). - Run with a small test file and
-p best-quality. - Check the Recipes & Common Workflows page for working examples.
- Open an issue on GitHub with the log output and the exact command you ran.
See Also¶
- Recipes & Common Workflows β many of the issues here are demonstrated with working examples.
- CLI Reference β full command options and profiles.
- Architecture Design β deeper internals that can help understand error cases.
- SDK & API Usage β programmatic usage.