Face Intel β Troubleshooting Guide
This guide covers common errors, their causes, and how to fix them. Organized by symptom β find your error message or behavior below.
First steps for any issue:
- Check
/health/providersβ are circuits open? Are providersnot_configured?- Check
/statsβ are failures climbing? Are cache hits 0?- Check the structured logs β every line carries
eid,pid,retry,statusfor tracing.- Check
/providersβ theerrorsfield shows manifest discovery failures.
Table of Contents
- Startup Failures
- Provider Not Available /
not_configured - Circuit Breaker Open
- Model Download Failures
- dlib Compilation Issues
- Selenium / Chrome Issues
- Cache Issues
- Database Issues
- Performance Tuning
- API / HTTP Errors
- Image Validation Errors
- Logging Issues
- Recovery Procedures
1. Startup Failures
Symptom: ModuleNotFoundError: No module named 'cv2'
Cause: OpenCV not installed.
Fix:
pip install opencv-python==4.9.0.80
If you see libGL.so.1: cannot open shared object file on Linux:
# Debian/Ubuntu
sudo apt install -y libgl1 libglib2.0-0
# Or install the headless variant (no GUI deps)
pip uninstall opencv-python
pip install opencv-python-headless==4.9.0.80
Symptom: ModuleNotFoundError: No module named 'pydantic_settings'
Cause: Wrong pydantic version. Face Intel requires pydantic 2.x
with the separate pydantic-settings package.
Fix:
pip install pydantic==2.6.1 pydantic-settings==2.2.1
Symptom: ImportError: cannot import name 'X' from 'Y' during build_container()
Cause: Circular import or a layer violation. The refactor enforces strict one-way dependency direction.
Fix:
- Run
python scripts/check_imports.pyto detect cycles. - Check the layer the failing module is in β it must only import
from layers below it (see
docs/ARCHITECTURE.mdΒ§1). - If you recently added a provider, ensure it imports only from
models/,utils/,config/,pipeline/,providers/base.py.
Symptom: App boots but /providers returns an empty list
Cause: All providers are disabled in settings, OR none of them match the manifest.
Fix:
Check
.env:grep FI_ENABLE .envCheck the loaded settings:
python -c "from config.settings import settings; print([k for k,v in settings.model_dump().items() if k.startswith('enable_') and v])"Check
/providersforerrors:curl -s http://localhost:8000/providers | jq '.errors'
2. Provider Not Available / not_configured
A provider shows up in /providers with status: "not_configured"
and available: false.
Cause 1: Optional Python dependency missing
Symptom:
{
"name": "insightface",
"status": "not_configured",
"available": false
}
/providers errors field:
{ "insightface": "missing dependency: No module named 'insightface'" }
Fix:
pip install insightface onnxruntime
# Also set in .env:
# FI_ENABLE_INSIGHTFACE=true
Restart and verify:
curl -s http://localhost:8000/providers/insightface | jq '.available'
# β true
Cause 2: API key not set
Symptom: Provider enabled in config but is_available() returns
False because the key is empty.
curl -s http://localhost:8000/providers/serpapi | jq '.'
# β {"name": "serpapi", "status": "not_configured", "available": false, ...}
Fix:
Edit
.env:FI_ENABLE_SERPAPI=true FI_SERPAPI_KEY=your_actual_key_hereRestart.
Verify:
curl -s http://localhost:8000/providers/serpapi | jq '.available' # β true
See docs/CONFIGURATION.md Β§17
for the full key-setup workflow for each paid provider.
Cause 3: Provider's is_available() raised an exception
Symptom: Provider shows not_configured but no manifest error is
recorded. The registry's info() method catches the exception and
returns available=False.
Fix: Add logging to your provider's is_available():
def is_available(self) -> bool:
try:
return self._net is not None
except Exception as e:
logger.warning(f"is_available() raised: {e}")
return False
Cause 4: Provider class name doesn't match manifest
Symptom: Manifest entry says class_name="MyProvider" but the
file declares class MyProviderV2. The registry will fail to
instantiate.
Fix: Match the class name exactly. See
docs/PROVIDERS.md Β§7.
Cause 5: Provider constructor raised
Symptom: Manifest error like "init error: RuntimeError(...)".
Fix: Check the constructor. Provider constructors must not raise
for missing optional deps β they should catch and store the error,
then return False from is_available():
def __init__(self, settings):
super().__init__(settings=settings)
self._init_error = None
try:
self._model = load_model()
except Exception as e:
self._init_error = str(e)
See the dnn provider
(providers/detection/dnn.py)
for the canonical pattern.
3. Circuit Breaker Open
Symptom
A provider that was previously working is now skipped β providers_invoked
no longer includes it, and /health/providers shows circuit_open: true:
{
"name": "serpapi",
"healthy": false,
"consecutive_failures": 5,
"circuit_open": true,
"avg_latency_ms": 0.0
}
How the circuit breaker works
See docs/CONFIGURATION.md Β§11.
Briefly:
- Provider fails β
consecutive_failures += 1. - When
consecutive_failures >= circuit_breaker_failure_threshold(default 5), the circuit opens. - After
circuit_breaker_recovery_seconds(default 120s), the circuit transitions to half-open: the next call is allowed. - Success β circuit closes. Failure β circuit re-opens.
Cause 1: Genuine provider failure
Diagnosis:
curl -s http://localhost:8000/stats | \
jq '.counters | to_entries | map(select(.key | startswith("failures.")))'
If failures.<provider> is climbing, the provider is genuinely
failing. Check its logs:
# Tail logs filtered to the provider
tail -f /var/log/face-intel/app.log | grep "pid=<provider_name>"
Common root causes:
- API key expired / revoked.
- Rate limited by upstream (429).
- Network partition.
- Upstream service is down.
Fix: Resolve the underlying issue. The circuit will close automatically on the next successful call after the recovery window.
Cause 2: Misconfigured retry policy
If retry_max_attempts is too low and the provider has transient
failures, you can hit the failure threshold quickly.
Fix: Tune retry:
FI_RETRY_MAX_ATTEMPTS=5
FI_RETRY_INITIAL_BACKOFF_SECONDS=1.0
FI_RETRY_MAX_BACKOFF_SECONDS=30.0
FI_CIRCUIT_BREAKER_FAILURE_THRESHOLD=10
FI_CIRCUIT_BREAKER_RECOVERY_SECONDS=300
Cause 3: Provider timeout too aggressive
If orchestrator_timeout_seconds=10 but the provider takes 15s on
average, every call times out β circuit opens.
Fix:
FI_ORCHESTRATOR_TIMEOUT_SECONDS=60
Manual reset
There's no API endpoint to manually close a circuit. Two options:
- Wait β the circuit auto-transitions to half-open after
circuit_breaker_recovery_seconds. - Restart β restart the process. The metrics are in-memory only; they reset on restart (the circuit starts closed).
For a programmatic reset (e.g. for testing), use the metrics facade directly:
container.metrics.health.reset()
Verifying recovery
# Trigger a single call to the provider
curl -X POST http://localhost:8000/faces/detect \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/face.jpg", "providers": ["<name>"]}'
# Check circuit state
curl -s http://localhost:8000/health/providers | \
jq '.providers[] | select(.name == "<name>") | .circuit_open'
4. Model Download Failures
Affected providers
dnnβ downloadsdeploy.prototxt(28 KB) and10.7 MB) on first use tores10_300x300_ssd_iter_140000.caffemodel(data/models/.insightfaceβ downloads model packs (~100-550 MB) on first use.deepfaceβ downloads backend weights on first use.
Symptom: urllib.error.URLError: <urlopen error ...>
Cause: No internet access from the host, or GitHub raw content is blocked.
Fix β pre-download the DNN model files:
mkdir -p data/models
curl -L -o data/models/deploy.prototxt \
https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20170830/deploy.prototxt
curl -L -o data/models/res10_300x300_ssd_iter_140000.caffemodel \
https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel
# Verify checksums (sha256)
sha256sum data/models/*
Then restart. The provider will see the files already exist and skip the download.
Symptom: Model file is corrupt (cv2.dnn.readNetFromCaffe fails)
Cause: Partial download (e.g. network interrupted).
Fix:
rm data/models/deploy.prototxt data/models/res10_300x300_ssd_iter_140000.caffemodel
# Restart β provider will re-download
Symptom: Downloads work but model fails to load
cv2.error: OpenCV(4.9.0) /io/opencv/modules/dnn/src/caffe/caffe_importer.cpp...
Cause: Corrupt file or OpenCV version mismatch.
Fix:
- Re-download (see above).
- Verify OpenCV version:
python -c "import cv2; print(cv2.__version__)". Must be4.9.0.80perrequirements.txt. - Try
opencv-python-headlessif GUI deps cause issues.
Symptom: InsightFace model pack download is very slow
Cause: InsightFace downloads from AWS S3 in the US-east region.
Fix: Pre-download on a build machine and bake into your Docker image:
RUN python -c "from insightface.app import FaceAnalysis; FaceAnalysis(name='buffalo_l').prepare(ctx_id=-1)"
This caches the model in ~/.insightface/models/. Copy that
directory into the production image.
Air-gapped deployment
For hosts with zero internet access:
- On a build machine with internet, run the app once to trigger all model downloads.
- Tar up
data/models/and~/.insightface/(or wherever the optional libs cache models). - Copy to the production host at the same paths.
- Restart β providers will find the models locally.
5. dlib Compilation Issues
dlib==19.24.2 does not ship prebuilt wheels for all platforms β
on Linux you'll often need to compile from source.
Symptom: error: command 'gcc' failed during pip install dlib
Cause: Missing build tools.
Fix (Debian/Ubuntu):
sudo apt install -y build-essential cmake python3-dev
pip install dlib==19.24.2
Fix (Alpine):
apk add --no-cache build-base cmake linux-headers
pip install dlib==19.24.2
Fix (macOS):
xcode-select --install
brew install cmake
pip install dlib==19.24.2
Symptom: Compilation hangs or runs out of memory
Cause: dlib compiles with all cores by default; on small VMs this OOMs.
Fix: Limit parallelism:
pip install --no-build-isolation dlib==19.24.2 \
--config-settings cmake.define.BUILD_SHARED_LIBS=OFF \
-j 2
Or use a machine with β₯4 GB RAM for the build step.
Symptom: ModuleNotFoundError: No module named 'dlib' after install
Cause: Wrong Python interpreter β dlib installed in a different venv than the one running Face Intel.
Fix:
which python # should be your venv python
python -m pip install dlib==19.24.2
python -c "import dlib; print(dlib.__version__)"
Symptom: face_recognition provider crashes with Illegal instruction
Cause: dlib compiled with AVX instructions but CPU doesn't support them (common on older Xeons / VPS hosts).
Fix: Recompile dlib without AVX:
pip uninstall dlib
git clone https://github.com/davisking/dlib.git
cd dlib
mkdir build && cd build
cmake .. -DUSE_AVX_INSTRUCTIONS=OFF -DCMAKE_BUILD_TYPE=Release
make -j 4
cd ..
python setup.py install
Alternative: skip dlib entirely
If you don't need recognition:
FI_ENABLE_FACE_RECOGNITION=false
Then pip uninstall dlib face-recognition to remove the broken
install. The other 23 providers continue to work.
6. Selenium / Chrome Issues
Affects: selenium (scraper), google_lens (reverse search).
Symptom: selenium.common.exceptions.WebDriverException: Message: unknown error: cannot find Chrome binary
Cause: Chrome/Chromium not installed.
Fix (Debian/Ubuntu):
# Stable Chrome
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install -y ./google-chrome-stable_current_amd64.deb
# Or Chromium
sudo apt install -y chromium-browser
Fix (macOS):
brew install --cask google-chrome
Fix (Docker): Add to Dockerfile:
RUN apt-get update && apt-get install -y chromium
ENV CHROME_BIN=/usr/bin/chromium
Symptom: selenium.common.exceptions.SessionNotCreatedException: Message: session not created: Chrome version must be between ...
Cause: webdriver-manager downloaded a ChromeDriver version
that doesn't match your installed Chrome.
Fix:
google-chrome --version # note the major version
# Then ensure chromedriver matches
pip install --upgrade webdriver-manager
Or use Chrome for Testing with pinned versions:
# Install Chrome for Testing 121
# https://googlechromelabs.github.io/chrome-for-testing/
Symptom: Selenium tests hang indefinitely
Cause: selenium_implicit_wait is too high, or Chrome is
waiting for a never-resolving resource.
Fix:
Lower implicit wait:
FI_SELENIUM_IMPLICIT_WAIT=5Reduce scroll iterations:
FI_SELENIUM_SCROLL_ITERATIONS=2Set a global scrape timeout:
FI_SCRAPE_TIMEOUT=15For debugging, run non-headless to see what's happening:
FI_SELENIUM_HEADLESS=false
Symptom: Headless Chrome returns empty pages
Cause: Some sites detect headless Chrome and serve different content. Or JavaScript hasn't finished rendering.
Fix:
Increase scroll iterations:
FI_SELENIUM_SCROLL_ITERATIONS=10Use a more realistic User-Agent:
FI_USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"Fall back to the
beautifulsoupscraper (static HTML) for sites that don't require JS:FI_ENABLE_SELENIUM_SCRAPER=false FI_ENABLE_BEAUTIFULSOUP_SCRAPER=true
Symptom: Chrome crashes with --no-sandbox warning in containers
Cause: Running Chrome as root in a container requires
--no-sandbox. Selenium providers should set this automatically;
if not, set in your Chrome options.
Fix: Make sure your selenium provider uses:
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage") # critical in containers
opts.add_argument("--headless=new")
The --disable-dev-shm-usage flag tells Chrome to use /tmp
instead of /dev/shm (which is too small in default containers).
7. Cache Issues
Symptom: Cache hit ratio is always 0
Cause: Cache is disabled, or every request uses a different
image (cache key is provider:image_hash).
Diagnosis:
curl -s http://localhost:8000/cache | jq
{
"entries": 0,
"max_entries": 1000,
"ttl_seconds": 3600,
"hits": 0,
"misses": 24,
"hit_ratio": 0.0,
"evictions": 0
}
Fix:
Ensure cache is enabled:
FI_CACHE_ENABLED=trueVerify you're hitting the same image. The cache key is
f"{provider_name}:{image_hash}"whereimage_hashis the SHA-256 of the preprocessed image. Slight image differences (URL vs base64, re-encoded JPEGs) produce different hashes.If you're load-testing with random images, expect 0% hit ratio.
Symptom: Cache evictions are high
Cause: cache_max_entries is too small for the working set.
Diagnosis:
curl -s http://localhost:8000/cache | jq '.evictions'
Fix:
FI_CACHE_MAX_ENTRIES=10000
Symptom: Same image returns different results across requests
Cause: Cache was cleared, or the TTL expired.
Diagnosis:
- Check
GET /cacheforclearedevents β but actually the cache doesn't expose clear count via API. Check the structured logs forcache clearevents. - Check
cache_ttl_secondsβ if it's very short, entries expire between requests.
Fix:
FI_CACHE_TTL_SECONDS=86400 # 24 hours
Symptom: Stale data β provider updated but cache still serves old result
Cause: Cache TTL hasn't expired.
Fix:
Clear the cache:
curl -X DELETE http://localhost:8000/cacheOr invalidate a specific key (not exposed via API β would need a new endpoint, see
docs/API_REFERENCE.mdΒ§9).For permanent fix, lower the TTL or implement a webhook that clears the cache when the upstream model updates.
Symptom: Memory usage grows unboundedly
Cause: Each cache entry stores a full ProviderResult including
the raw field, which can be large for image-analysis providers.
Fix:
Lower
cache_max_entries:FI_CACHE_MAX_ENTRIES=100For multi-worker deployments, replace the in-memory cache with Redis (see
docs/DEPLOYMENT.mdΒ§9).If you can afford recomputation, disable the cache entirely:
FI_CACHE_ENABLED=false
8. Database Issues
Symptom: sqlite3.OperationalError: database is locked
Cause: SQLite writer contention β multiple workers writing concurrently.
Fix:
- Run a single uvicorn worker (no
--workers N). - Or shard: different DB paths per worker (each worker serves a subset of requests).
- Or migrate to Postgres (would require a new
Databaseimplementation β not currently supported).
Symptom: sqlite3.OperationalError: unable to open database file
Cause: The FI_DB_PATH directory doesn't exist or isn't
writable.
Fix:
# Check the path
python -c "from config.settings import settings; print(settings.db_path)"
# Ensure the directory exists
mkdir -p $(dirname $(python -c "from config.settings import settings; print(settings.db_path)"))
# Check permissions
ls -la $(dirname $(python -c "from config.settings import settings; print(settings.db_path)"))
Symptom: Jobs disappear from /jobs after a restart
Cause: Using :memory: DB by accident.
Fix: Check your .env:
grep FI_DB_PATH .env
# Should NOT be:
# FI_DB_PATH=:memory:
Symptom: /jobs/{id} returns 404 even though the job ran
Cause: The job was created but persistence failed silently (the service catches the exception and continues).
Diagnosis: Check the structured logs for eid=<your job id>.
Fix: If the DB write is failing, you'll see the error in logs. Common causes: disk full, DB locked (see above), schema mismatch.
Symptom: DB file is huge
Cause: Old jobs and results accumulated.
Fix:
Run cleanup:
python -c " from config.settings import settings from storage.database import Database db = Database(path=settings.db_path) n = db.cleanup_old_jobs(settings.job_retention_days) print(f'Deleted {n} old jobs') "Compact the file:
sqlite3 /var/lib/face-intel/face_intel.db "VACUUM;"Schedule cleanup as a cron job β see
docs/DEPLOYMENT.mdΒ§5.
9. Performance Tuning
Symptom: Detection jobs take >500 ms
Diagnosis: Check /stats for per-provider latency:
curl -s http://localhost:8000/stats | \
jq '.providers[] | {name, avg_latency_ms, p95_latency_ms}'
Causes & fixes:
| Cause | Fix |
|---|---|
dnn running on CPU |
Add CUDA-enabled OpenCV (opencv-python with CUDA build), or use haar for fast pre-filtering. |
mtcnn running on CPU |
Switch to dnn for speed (MTCNN is more accurate but slower). |
| Image is huge (e.g. 4K) | Pre-resize before upload, or lower pipeline.ImagePreprocessor.max_dim (currently hardcoded to 1024). |
| Many providers invoked | Use the providers whitelist to invoke only what you need. |
| Orchestrator concurrency too low | Raise FI_ORCHESTRATOR_MAX_CONCURRENCY=16. |
Symptom: Full-pipeline jobs take >30 s
Cause: Six concurrent sub-jobs (detection, recognition, image
analysis, metadata, forensics, search) all running serially within
asyncio.gather.
Fix:
Disable providers you don't need. If you're not doing reverse search, disable all
SCRAPINGandREVERSE_SEARCHproviders:FI_ENABLE_BEAUTIFULSOUP_SCRAPER=false FI_ENABLE_SELENIUM_SCRAPER=false FI_ENABLE_DUCKDUCKGO_SCRAPER=false FI_ENABLE_GOOGLE_LENS=falseIncrease orchestrator concurrency:
FI_ORCHESTRATOR_MAX_CONCURRENCY=32Increase job timeout if needed:
FI_JOB_TIMEOUT_SECONDS=600
Symptom: High memory usage
Causes & fixes:
| Cause | Fix |
|---|---|
| Many face crops in memory | Lower pipeline.ImagePreprocessor.max_dim. |
| Cache storing large raw responses | Lower FI_CACHE_MAX_ENTRIES. |
| InsightFace / DeepFace loaded models | Disable if not needed. |
| Selenium Chrome processes | Disable Selenium if not needed. |
| Memory leak in custom provider | Profile with tracemalloc. |
Symptom: High CPU usage at idle
Cause: Background polling. Currently Face Intel has no background tasks, so idle CPU should be ~0%. If you see sustained CPU:
Check for runaway Chrome processes (
ps aux | grep chromium).Check for stuck asyncio tasks (look in the structured logs for
eidvalues that never reachsuccessorfailed).Profile with
py-spy:py-spy top --pid <face-intel-pid>
Symptom: Network-bound providers are slow
Causes & fixes:
| Cause | Fix |
|---|---|
| High latency to upstream API | Use a CDN or proxy closer to the upstream. |
| Rate-limited by upstream | Lower reverse_search_max_results to reduce call size. |
| Connection pool exhausted | The shared session uses 10 connections per host. For higher concurrency, modify utils/http.py::make_session() to bump pool_maxsize. |
| DNS lookups slow | Configure a local DNS cache (systemd-resolved, dnsmasq). |
Recommended production settings
FI_ORCHESTRATOR_MAX_CONCURRENCY=16
FI_ORCHESTRATOR_TIMEOUT_SECONDS=60
FI_RETRY_MAX_ATTEMPTS=3
FI_CACHE_ENABLED=true
FI_CACHE_TTL_SECONDS=86400
FI_CACHE_MAX_ENTRIES=10000
FI_CIRCUIT_BREAKER_FAILURE_THRESHOLD=5
FI_CIRCUIT_BREAKER_RECOVERY_SECONDS=120
10. API / HTTP Errors
429 Too Many Requests
Cause: Per-IP rate limit exceeded (default 30/min).
Fix:
Raise the limit:
FI_RATE_LIMIT_PER_MINUTE=120Or exclude specific IPs (would require a code change to
RateLimitMiddleware).For multi-process deployments, the in-memory limiter is per process β each worker allows
rate_limit_per_minute. Replace with Redis-backed limiter for accurate cross-process limiting.
413 Payload Too Large
Cause: Request body exceeded FI_MAX_REQUEST_BODY_BYTES
(default 25 MB).
Fix:
Compress the image before upload.
Or raise the limit:
FI_MAX_REQUEST_BODY_BYTES=52428800 # 50 MBAlso raise
FI_MAX_IMAGE_BYTESif the decoded image is being rejected (default 20 MB):FI_MAX_IMAGE_BYTES=41943040 # 40 MB
422 Unprocessable Entity
Cause: Pydantic validation failed on the request body.
Fix: Check the response body for the validation error details:
curl -X POST http://localhost:8000/faces/detect \
-H "Content-Type: application/json" \
-d '{"bad": "request"}' -i
The response will include the exact field that failed:
{
"detail": [
{
"loc": ["body", "image_url"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
500 Internal Server Error
Cause: Unhandled exception. GlobalExceptionMiddleware catches
it and returns:
{
"success": false,
"error": "<exception message>",
"error_type": "<ExceptionClassName>",
"request_id": "abc123def456"
}
Fix:
Note the
request_id.Find the matching log entries:
grep "eid=abc123def456" /var/log/face-intel/app.logThe stack trace will be in the log thanks to
logger.exception().
GET /jobs/{id} returns 404 even though POST /jobs returned a job_id
Cause: Job ID mismatch, or the DB write failed silently.
Fix:
- Verify the job ID you're passing (copy-paste from the POST response).
- Check the structured logs for
eid=<job_id>to see if the job actually persisted.
11. Image Validation Errors
The InputValidator (see pipeline/validation.py)
returns ValidationError for these cases:
"No image input provided."
Cause: Neither image_url nor image_base64 was set on the
request.
Fix: Pass at least one:
{"image_url": "https://example.com/photo.jpg"}
"Unsupported URL scheme: ftp"
Cause: Only http and https schemes are allowed.
Fix: Use an HTTPS URL.
"URL missing host."
Cause: Malformed URL like http:///path.
Fix: Use a well-formed URL.
"Localhost URLs not permitted."
Cause: URL host is localhost, 127.0.0.1, 0.0.0.0, or
::1. This is a defense against SSRF attacks.
Fix: Use a publicly resolvable URL. For testing locally, host
the image with a tool like ngrok or use base64 encoding.
"Invalid base64: ..."
Cause: Base64 string couldn't be decoded.
Fix: Verify the base64 is valid:
echo "your_base64_string" | base64 -d | file -
# Should print "JPEG image data, ..."
If you're including the data:image/jpeg;base64, prefix, that's
fine β the validator strips it.
"Decoded image exceeds 20971520 bytes"
Cause: Image is larger than FI_MAX_IMAGE_BYTES (default 20 MB).
Fix: Compress or resize the image, or raise the limit.
"Unrecognized image format (magic bytes mismatch)."
Cause: The bytes don't match any recognized image signature (JPEG, PNG, GIF, WEBP, BMP, TIFF).
Fix: Verify the file is actually an image:
file your_image.jpg
# Should print "JPEG image data, ..."
Common cause: the URL returned an HTML error page instead of an image. Check the URL in a browser first.
12. Logging Issues
Symptom: Logs are too verbose
Fix:
FI_LOG_LEVEL=WARNING
Levels: TRACE < DEBUG < INFO < WARNING < ERROR <
CRITICAL. Most production setups use INFO.
Symptom: Logs are too quiet (can't see provider invocations)
Fix:
FI_LOG_LEVEL=DEBUG
Note: DEBUG will include every cache hit/miss and orchestrator
decision. Use TRACE only for active debugging.
Symptom: JSON logs have escaped quotes
Cause: The JSON format string in utils/logging.py uses
single-quote wrapping around double-quoted JSON. This is correct
loguru behavior β the output is valid JSON, just visually escaped
when viewed in some terminals.
Fix: Pipe through jq:
tail -f /var/log/face-intel/app.log | jq .
Symptom: eid and pid fields are always -
Cause: The log was produced outside an execution_context()
block. This is normal for startup/shutdown logs. If you see it for
provider invocation logs, the context isn't being propagated β
check that your provider is invoked through the orchestrator (which
sets the context) rather than directly.
Symptom: Logs from loguru aren't picked up by systemd journal
Cause: loguru writes to sys.stderr by default. systemd
captures stderr, but only if the unit doesn't redirect it.
Fix: Either:
Let systemd capture stderr (default behavior β check with
journalctl -u face-intel -f).Or have loguru write to a file directly by extending
setup_logging():logger.add("/var/log/face-intel/app.log", format=fmt, level=settings.log_level)
13. Recovery Procedures
Blackout recovery: all providers failing
If every provider circuit is open:
Check
/health/providers:curl -s http://localhost:8000/health/providers | \ jq '.providers[] | select(.circuit_open == true) | .name'Identify the common cause (network down? disk full? CPU pegged?).
Fix the underlying issue.
Restart the app (resets all circuits).
Cache corruption
If the cache is returning bad data:
curl -X DELETE http://localhost:8000/cache
# Returns: {"cleared": <N>}
This clears all entries. The next request to each provider will re-populate the cache.
Database corruption
If SQLite reports corruption:
Stop the app.
Backup the corrupt file (for forensics):
cp /var/lib/face-intel/face_intel.db /tmp/face_intel.corrupt.dbTry to recover:
sqlite3 /var/lib/face-intel/face_intel.db ".recover" > /tmp/recovered.sql sqlite3 /var/lib/face-intel/face_intel.new.db < /tmp/recovered.sql mv /var/lib/face-intel/face_intel.new.db /var/lib/face-intel/face_intel.dbIf recovery fails, delete the DB and restart (you lose job history but the app re-creates an empty schema):
rm /var/lib/face-intel/face_intel.db systemctl restart face-intel
Gallery corruption
If data/gallery/manifest.json is corrupt:
The
ReferenceStoreconstructor catches the JSON decode error and starts fresh with an empty gallery. You'll see a warning log:Gallery manifest corrupted, starting freshTo restore, re-add known persons via the service:
from api.container import build_container from config.settings import settings container = build_container(settings) # Add each person back... container.recognition_service.add_known_person("alice", embedding_bytes)If you have backups of the
.npyfiles, restore them todata/gallery/and rebuildmanifest.jsonmanually.
Provider stuck in not_configured after a fix
If you've installed the missing dep or set the API key but the
provider still shows not_configured:
Restart the app. The registry only runs
discover()at startup β it doesn't re-import providers later.Verify the env var is loaded:
python -c "from config.settings import settings; print(settings.enable_insightface, settings.insightface_model_pack)"Check
/providerserrorsfor that provider.
Diagnostic Cheat Sheet
| Symptom | First command to run |
|---|---|
| "Provider X not working" | curl -s http://localhost:8000/providers/X | jq |
| "Job failed" | curl -s http://localhost:8000/jobs/<id>/result | jq |
| "All providers failing" | curl -s http://localhost:8000/health/providers | jq '.providers[] | select(.circuit_open)' |
| "Slow responses" | curl -s http://localhost:8000/stats | jq '.providers[] | {name, p95_latency_ms}' |
| "Cache not working" | curl -s http://localhost:8000/cache | jq |
| "Memory growing" | ps -o pid,rss,vsz,cmd -p <face-intel-pid> |
| "Disk filling" | du -sh data/* |
| "Mystery error" | grep "eid=<request_id>" /var/log/face-intel/app.log |
See Also
docs/CONFIGURATION.mdβ full settings table for tuning circuit breaker, cache, retry, timeouts.docs/PROVIDERS.mdβ provideris_available()patterns and error handling.docs/DEPLOYMENT.mdβ operational runbook, backup procedures.docs/API_REFERENCE.mdΒ§5 β error response envelope.docs/ARCHITECTURE.mdΒ§4.3 β circuit breaker lifecycle diagram.