Troubleshooting
Getting started
Docker: port conflict (5432 or 5434)
The development Compose file (docker-compose.local.yml) maps Postgres to port 5434 to avoid conflicts with local Postgres installations (the default docker-compose.yml uses port 5432). If you see:
Error: starting container failed: port is already allocated
Check what’s running on port 5432 (your local Postgres) and 5434 (Docker). Either stop the conflicting service or change the port mapping in docker-compose.local.yml.
Alembic: migration fails with StringDataRightTruncationError
SQLAlchemyError: (psycopg2.errors.StringDataRightTruncationError) value too long for type character varying(32)
Branch-based Alembic migrations can produce revision IDs longer than 32 characters. Recreate the alembic_version table with a wider column:
docker compose exec db-local psql -U modulo -c "DROP TABLE IF EXISTS alembic_version; CREATE TABLE alembic_version (version_num VARCHAR(255) NOT NULL PRIMARY KEY);"
cd backend
uv run alembic upgrade heads
CORS (Cross-Origin Resource Sharing): frontend can’t reach the API
Access to fetch at 'http://localhost:8000' from origin 'http://localhost:5173' has been blocked by CORS policy
Set CORS_ORIGINS=http://localhost:5173 in your backend/.env. Restart the backend. For production, include your domain: CORS_ORIGINS=https://modulo.run.
Redis: connection refused
Error connecting to Redis: connection refused
Redis is required: Modulo uses it for event coordination, rate limiting, caching, and session state, so there is no supported way to run without it. Two cases behave differently at startup:
- Empty
REDIS_URL(the variable is set but blank) fails startup with an explicitREDIS_URL is requirederror. Set it to a reachable instance instead of leaving it blank. - Unset
REDIS_URLfalls back to the defaultredis://localhost:6379/0, which may not be the instance you meant to use. If the backend runs on the host rather than inside the Compose network, setREDIS_URL=redis://localhost:6380/0inbackend/.envto reach the Compose-published Redis.
docker compose -f docker-compose.local.yml up -d
This starts both Postgres (port 5434) and Redis (port 6380). The backend/.env value above points the host-running backend at the Compose-published Redis.
Configuration
Login: admin:admin doesn’t work
Check that:
MODULO_USERS=admin:adminis set in yourbackend/.envfile- The backend started after the
.envwas created (restart if needed)
Fernet key: malformed
ValueError: Fernet key must be 32 url-safe base64-encoded bytes
Generate a fresh key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Set the output as FERNET_KEY in your .env. Do not use the example key from the docs.
GitHub connector: authentication fails
Verify:
- The token has the correct scopes:
repofor private reposcontents: readfor file accesspull_requests: writefor PR comments
- The token hasn’t expired
- The connector config in the UI matches the token’s owner and repo
Running pipelines
Pipeline: stuck at human-in-the-loop (HITL) checkpoint
A HITL checkpoint pauses the pipeline until someone claims and approves it.
- Open the Pipeline Runs view
- Find the paused run (status:
awaiting_human) - Click Review to inspect the artifact
- Claim the review request (15-minute exclusive window)
- Approve to continue or Reject to stop
If no reviewers are configured, add a default reviewer in pipeline settings.
Pipeline: run stuck in pending
A run stays pending when it has no capacity slot. That is normal, and by default the run waits indefinitely: there is no queued timeout that fails it, and nothing is silently dropped.
- Check whether any run of the pipeline is moving from
pendingtorunning. If none ever starts, the worker service is the problem, not a limit. - Read the Capacity and queue panel on Analytics. It shows pending depth, the oldest wait, the drain ratio, which limit is binding, and a breakdown of pending runs by cause (pipeline capacity, org run capacity, org sandbox capacity).
- Compare the binding limit against the values on Runners > Concurrency and the pipeline’s
max_concurrent_runs. - Check human gates. A backlog of undecided gates holds organization capacity while holding no pipeline slot.
If the run ends rejected with error code queue.expired, the pipeline has a queue_timeout_seconds set and the run waited past it. That is a supersession, not a failure: the run was dropped before it started and is excluded from failure counts. Raise or clear queue_timeout_seconds if the wait should be tolerated. See Concurrency and capacity sizing.
Related
- Configuration: environment variables and backend setup
- Installation: initial setup and Docker Compose
- Human-in-the-Loop: claim and review workflow details
- Connectors: connector credential setup
- Secrets: encryption key handling behind the Fernet and credential fixes above
- Single Sign-On: SSO provider setup and the login flow