Skip to main content

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 explicit REDIS_URL is required error. Set it to a reachable instance instead of leaving it blank.
  • Unset REDIS_URL falls back to the default redis://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, set REDIS_URL=redis://localhost:6380/0 in backend/.env to 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:admin is set in your backend/.env file
  • The backend started after the .env was 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:
    • repo for private repos
    • contents: read for file access
    • pull_requests: write for 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.

  1. Open the Pipeline Runs view
  2. Find the paused run (status: awaiting_human)
  3. Click Review to inspect the artifact
  4. Claim the review request (15-minute exclusive window)
  5. 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.

  1. Check whether any run of the pipeline is moving from pending to running. If none ever starts, the worker service is the problem, not a limit.
  2. 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).
  3. Compare the binding limit against the values on Runners > Concurrency and the pipeline’s max_concurrent_runs.
  4. 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.