Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jtapieromalambo-ctrl/Signia/llms.txt

Use this file to discover all available pages before exploring further.

Most Signia deployment problems fall into a small number of categories: a missing or untrained ML model, an environment variable that was left unset, a misconfigured static file backend, or a misunderstanding about how Railway’s ephemeral filesystem interacts with the application. The expandable sections below document each known issue with its exact error, root cause, and the precise fix to apply.
Error
503 Service Unavailable
The /reconocimientos/camara/ page loads but every recognition request immediately returns a 503 response. In Railway logs you may see:
FileNotFoundError: [Errno 2] No such file or directory: '.../reconocimientos/modelo/model_seq.pkl'
CauseThe reconocimientos/views.py module attempts to load the RandomForest classifier at import time. If reconocimientos/modelo/model_seq.pkl does not exist — because the model has never been trained, or because a Railway deployment wiped the ephemeral filesystem — the module sets the classifier to None and returns 503 for every recognition request.Solution
  1. Log in with a superuser account.
  2. Navigate to /admin-videos/.
  3. Upload at least one sign video per label.
  4. Click Entrenar modelo (Train model).
  5. Wait for the training process to complete. Training runs in a background daemon thread; the page will indicate when it is done.
After training, the following files are written to disk:
reconocimientos/
└── modelo/
    ├── model_seq.pkl       ← RandomForest classifier (300 trees)
    └── label_encoder.npy   ← Label encoder array
Railway’s filesystem is ephemeral. These files are deleted on every new deployment. You must retrain the model after each deploy to re-enable camera recognition.
Error
403 Forbidden
CSRF verification failed. Request aborted.
This error appears on any POST request — login, registration, translation, recognition — after deploying to Railway.CauseDjango’s CSRF middleware validates that the Origin or Referer header of a POST request matches a domain in CSRF_TRUSTED_ORIGINS. By default, settings.py only populates CSRF_TRUSTED_ORIGINS when RAILWAY_PUBLIC_DOMAIN is set. If that variable is missing, no Railway domain is trusted and every form submission is rejected.SolutionAdd RAILWAY_PUBLIC_DOMAIN to your Railway service’s environment variables:
RAILWAY_PUBLIC_DOMAIN=your-app.up.railway.app
settings.py reads this value and dynamically builds the trusted origins list:
railway_domain = config('RAILWAY_PUBLIC_DOMAIN', default='')
if railway_domain:
    ALLOWED_HOSTS.append(railway_domain)
    CSRF_TRUSTED_ORIGINS = [f'https://{railway_domain}']
Redeploy after adding the variable. The CSRF error will disappear immediately on the next deployment.
Error (browser console)
GET https://your-app.up.railway.app/static/js/vendor/mediapipe/wasm/vision_wasm_internal.wasm 404 (Not Found)
or
416 (Requested Range Not Satisfiable)
RuntimeError: Failed to fetch the Wasm module
The camera view loads but no hand landmarks are ever detected, and the MediaPipe HandLandmarker never initialises.CauseThere are two distinct causes for these two errors:
  • 404: The STORAGES setting was changed to ManifestStaticFilesStorage or WhiteNoiseStorage. Both backends rename collected files by appending a content hash (e.g. vision_wasm_internal.abc123.wasm). MediaPipe’s internal JavaScript loader constructs the WASM filename with a hard-coded string and cannot follow the manifest, so it requests the original name and gets a 404.
  • 416: The .wasm extension was removed from WHITENOISE_SKIP_COMPRESS_EXTENSIONS. WhiteNoise compressed the WASM binary with gzip, and Chromium’s range-request mechanism cannot satisfy a byte-range request against a gzip stream.
SolutionVerify that settings.py contains exactly these two entries and has not been modified:
STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
    },
}

WHITENOISE_SKIP_COMPRESS_EXTENSIONS = (
    'jpg', 'jpeg', 'png', 'gif', 'webp', 'zip', 'gz', 'tgz', 'bz2', 'tbz', 'xz',
    'br', 'swf', 'flv', 'woff', 'woff2', '3gp', '3gpp', 'asf', 'avi', 'm4v',
    'mov', 'mp4', 'mpeg', 'mpg', 'webm', 'wmv', 'wasm', 'task'
)
After correcting the settings, run python manage.py collectstatic --no-input --clear (Railway does this in build.sh) and redeploy.
SymptomCamera recognition works for one user but hangs indefinitely or returns empty results when multiple requests arrive concurrently. Railway logs may show Gunicorn worker timeouts.CauseMediaPipe’s HandLandmarker is not thread-safe. A single shared instance accessed by multiple Gunicorn worker threads simultaneously causes deadlocks, dropped frames, and silent hangs. This is a known limitation of the MediaPipe C++ runtime layer.SolutionThe code in reconocimientos/views.py already handles this correctly using threading.local(), which creates a separate HandLandmarker instance for each worker thread:
_thread_local = threading.local()

def get_detector():
    if not hasattr(_thread_local, 'detector'):
        _thread_local.detector = HandLandmarker.create_from_options(options)
    return _thread_local.detector
Do not refactor this into a module-level singleton. If you see deadlocks, verify that no other part of the codebase imports and reuses a single HandLandmarker instance across requests.
Error
django.db.utils.OperationalError: SSL connection has been closed unexpectedly
or
psycopg2.OperationalError: FATAL: SSL required for user "..."
CauseNeon requires all connections to use SSL. If DATABASE_URL does not include ?sslmode=require, the connection is rejected or dropped.SolutionEnsure your DATABASE_URL ends with ?sslmode=require:
DATABASE_URL=postgresql://user:password@ep-xxx.us-east-2.aws.neon.tech/signia?sslmode=require
settings.py also enforces SSL through the OPTIONS dictionary as a second layer of protection:
DATABASES = {
    'default': {
        **dj_database_url.parse(config('DATABASE_URL', ...)),
        'DISABLE_SERVER_SIDE_CURSORS': True,
        'OPTIONS': {
            'sslmode': 'require',
            'connect_timeout': 10,
            ...
        },
    }
}
DISABLE_SERVER_SIDE_CURSORS = True is required for Neon’s connection pooler (PgBouncer). Do not remove it; server-side cursors are incompatible with Neon’s pooling layer and cause InvalidCursorName errors.
Error (Django logs)
sib_api_v3_sdk.rest.ApiException: (401) Reason: Unauthorized
or no error at all — emails are accepted by Django but never arrive in the inbox.CauseThere are three common causes:
  1. BREVO_API_KEY is missing, expired, or copied incorrectly.
  2. The sender address (DEFAULT_FROM_EMAIL) has not been verified in the Brevo dashboard.
  3. EMAIL_HOST_USER is set to an address that differs from the verified sender.
Solution
  1. Log in to brevo.comSettings → API Keys and confirm the key is active. Copy it exactly into BREVO_API_KEY.
  2. Go to Senders & IP → Senders and verify that osorioescobardavidfelipe@gmail.com (or the address you set in DEFAULT_FROM_EMAIL) appears with Verified status.
  3. Confirm the environment variables are consistent:
BREVO_API_KEY=xkeysib-...
EMAIL_HOST_USER=your-verified-sender@gmail.com
  1. Check Railway logs for the exact ApiException message — Brevo returns descriptive error bodies that identify the specific problem (rate limit, unverified sender, invalid key, etc.).
Error (Google OAuth consent screen)
Error 400: redirect_uri_mismatch
The redirect URI in the request, http://your-app.up.railway.app/accounts/google/login/callback/,
does not match the ones authorized for the OAuth client.
CauseRailway terminates HTTPS at its edge proxy and forwards requests to Gunicorn over plain HTTP internally. Django sees an HTTP request and generates http:// redirect URIs. Google’s OAuth service rejects these because the authorised URIs in the Google Cloud Console use https://.Solutionsettings.py already contains the fix:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
This tells Django to trust the X-Forwarded-Proto: https header that Railway’s proxy injects, causing Django to treat all requests as HTTPS and generate https:// redirect URIs.Verify you have not removed or commented out this line. Additionally, confirm that the authorised redirect URI in Google Cloud ConsoleAPIs & Services → Credentials includes:
https://your-app.up.railway.app/accounts/google/login/callback/
Error
FileNotFoundError: [Errno 2] No such file or directory: 'ffmpeg'
or
faster_whisper.WhisperError: ffmpeg was not found...
Occurs when a user submits an audio file via the /traduccion/ module and Whisper cannot locate the ffmpeg binary to decode it.Causefaster-whisper calls ffmpeg as a subprocess. On Railway, ffmpeg is installed by nixpacks.toml and is available on the system PATH. Locally on Windows, the project bundles ffmpeg binaries in the ffmpeg/ directory at the project root. If that directory is moved, renamed, or deleted, local development breaks.Solutiontraduccion/views.py prepends the bundled ffmpeg/ folder to PATH at the top of the module so that Whisper finds it before looking at system paths:
import os
import sys

# Prepend bundled ffmpeg directory to PATH
ffmpeg_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ffmpeg')
os.environ['PATH'] = ffmpeg_dir + os.pathsep + os.environ.get('PATH', '')
Do not move or rename the ffmpeg/ directory. On Railway, this code is harmless — the directory may not exist, but the system ffmpeg installed by nixpacks.toml is already on PATH and takes precedence.If you are on Linux or macOS for local development and do not have ffmpeg installed globally, install it via your package manager:
# macOS
brew install ffmpeg

# Ubuntu / Debian
sudo apt-get install ffmpeg

Build docs developers (and LLMs) love